『Java言語で学ぶデザインパターン入門』をPHPで実習する State

増補改訂版Java言語で学ぶデザインパターン入門

本記事に掲載したサンプルコードは、https://github.com/ryo-utsunomiya/design_patternでも公開中です。

Stateとは

「状態」をクラスとして表現する方法です。複雑な条件文をリファクタリングする際などに使用されます。

ここでは、「昼」「夜」という状態を実装し、状態に応じて表示されるメッセージが変わるアプリケーションを実装します。

まずは、Stateのインタフェース。

doClockメソッドで使用しているContextInterfaceは、状態に応じた振る舞い(この例では、メッセージ出力)を行うオブジェクトのインタフェースです。定義は以下のようになります。

次に、昼を表すDayState

DayStateはSingletonで実装しています。これは、状態が変わる度に新しいインスタンスが生成されることを防ぐためです。また、isDayというメソッドを持っていて、渡された時刻が昼か夜かを判別します。

昼か夜かを判別する役割をどのクラスに持たせるかは悩みましたが、DayStateに持たせました。今回はオリジナルに準拠してStateをインタフェースにしていますが、抽象クラスを作って、これに昼か夜かを判別するメソッドを持たせるのが良いかもしれません。

次に、夜を表すNightState

次に、DayState・NightStateを使うSafe(金庫)クラスです(オリジナルではGUIを含んでいますが、PHPでGUIを作るのは大変なので割愛します)。

最後に、これらのクラスを使用するmain.phpです。

現在時刻の変化に応じて、Stateが自動的に切り替わります。状態が2つだけだと、ありがたみがわかりづらいですが、Stateパターンの強みは新しい状態の追加のような仕様変更に柔軟に対応できる、という点にあります。

『AngularJS: Up and Running』読書メモ 第10章 ngRouteによるルーティング

AngularJS: Up and Running

AngularJSはngRouteというモジュールをオプションで提供している。これを使うと、AngularJSアプリケーションでルーティング(URLに応じた画面遷移)を実装することができる。

※本書が執筆された当時はngRouteが唯一の公式ルーターだったが、2015年にNew Routerというルーターが導入されている。このルーターはAngularJS の1.4以上と2系のいずれでも使えるという特徴がある。

シングルページアプリケーションのルーティング

シングルページアプリケーションにおけるルーティングにおいては、URLとは通常のURLではなく、シバンの追加されたURLである。通常のURLは以下のようなスタイルである。

  • http://example.com/first/page

これに対して、シングルページアプリケーションのURLは以下のようになる。

  • http://example.com/#/first/page
  • http://example.com/#!/first/page

このようになる理由は、ブラウザはハッシュを含むURLを、含まないURLとは違った形で解釈するからである。ブラウザは、#の後ろにある部分をサーバへのリクエストの際に無視する。そのため、#の後ろの部分が変化しても、サーバへのリクエストは行われない。

ngRouteを使う

AngularJSのルーティングモジュールを使う手順は以下のとおり。

1 モジュールのソースコードを読み込む

2 モジュールの依存関係を定義する

3 ページ内のどの部分をAngularJSが変更すべきか印をつける。ngRouteを使う場合、ng-viewディレクティブをHTML内で使う。

4 $routeProviderサービスを使ってconfigセクションでルーティングを定義する

$routeProviderを使うことで、各ルートをwhen()メソッドで定義できる。when()メソッドは、2つの引数をとる。

  • 第1引数はURL、またはこのルートを適用すべきURLの正規表現
  • 第2引数は設定オブジェクトで、そのルートで何が起きるべきかを指定する

otherwize()関数を使うことで、未定義のルートにユーザが遷移しようとした時の振る舞いを決めることができる。

ルーティングオプション

$routeProvider.whenメソッドの第2引数には以下のような形でオプションを渡すことができる。

  • url: URLまたはURLの正規表現。/user/:userId のようにプレースホルダーを設定することも可能
  • template: HTML文字列
  • templateUrl: テンプレートへのパス
  • controller: コントローラー名の文字列又はコントローラーを定義する関数・配列
  • controllerAs: テンプレート内でアクセスするためのコントローラーの別名
  • redirectTo: 別のルートにリダイレクトする
  • resolve: ルートが実行される前に実行される非同期の処理を記述する(認可、認証等に使う)

resolveによるルート実行前のチェック

resolveによって、ルートが読み込まれる前に実行される非同期の処理を定義することができる。

2番目のルート/protectedには、resolveが定義されている。連想配列にimmediateasyncというキーが定義されているが、キーには任意の値を使用できる(myKey1, myKey2でも何でもいい)。連想配列の値は、AngularJSのDIのシンタックスの配列になる。

ルーティングのresolveについて、AngularJSは以下を保証する。

  • resolveが値を返した場合、AngularJSはこの関数の実行を完了し、成功したresolveとして扱う
  • resolveがpromiseオブジェクトを返した場合、AngularJSはpromiseが値を返すのを待って、promiseの結果に応じて成功/失敗として扱う
  • 全てのresolve関数が完了するまで、ルートは読み込まれない
  • 全てのresolveは並列に実行される
  • いずれかのresolveがエラーに遭遇するか、promiseがrejectされたら、AngularJSはそのルートを読み込まない

上のサンプルコードでは、immediateのresolveは、定数を返すだけなので、毎回成功する。asyncはサーバとの通信を行い、成功すれば、ルートが読み込まれる。失敗すると、コントローラーは読み込まれず、対応するHTMLも表示されない。この場合、ユーザーは最後に表示されたページに留まる。

もう一点重要なことは、それぞれのresolveの戻り値は、それぞれのキーによってコントローラーに注入される、ということだ。上のサンプルコードでは、immediateは数値、asyncはpromiseオブジェクトになる。

$routeParamsサービスを使う

Single Page Application(SPA)では、ルートのコンテキストが必要になることが多い。たとえば、/users/1に対して、id=1のユーザの情報を表示する、といった処理が必要になる。

理想的には、コントローラーとルートは、独立してアクセス可能であるべきである。これらのURLパラメータには、$routeParamsサービスを使ってアクセスできる。

このルートにおける、:detIdはプレースホルダーである。ここにある値は$routeParams.detIdで取得できる。

※ngRouteにはURLに制約を付ける機能がないため、/detail/1だけでなく/detail/fooでもアクセスできる。後者の場合、detIdは’foo’になる。

また、URLに含まれるクエリストリングも取得できる。/detail/1?q=fooにアクセスすると、以下のようなデータが取得できる。

注意すべきこと

空のテンプレート

ngRouteは、それぞれのルートが空ではないtemplateまたはtemplateUrlに関連付けられていることを要求する。テンプレートが存在しない場合、ngRouteはこのルートを無視する。空文字列のtemplateも、テンプレートが存在しないとみなされるので注意が必要である。

resolveによるコントローラーへの注入

resolveを使ってコントローラーに値を注入する際は、ng-controllerディレクティブを使用せず、ルーティング定義の一部としてコントローラーを定義すること。そうしないと、AngularJSは依存している値が取得できない。

$routeParamsの値の型

$routeParamsから取得した値は、デフォルトでは全て文字列になる。

1つのアプリケーションにng-viewは1つだけ

ngRouteでは、ng-viewは1つだけ使用可能である。複数のng-viewや、ネストしたng-viewは使用できない。

追加の設定

HTML5モード

ハッシュの含まれるURLはSPAでは一般的なものであるが、ハッシュが含まれない「普通の」URLを使うこともできる。

AngularJSのHTML5モードは、ブラウザのpushState APIを使用する。http://example.com/#/user/1というURLは、HTML5モードではhttp://example.com/user/1となる。

HTML5モードを有効化するには、サーバ側のサポートも必要である。ユーザがhttp://example.com/user/1にアクセスしたとき、サーバのドキュメントルート配下の/user/1ではなく、index.htmlが返るようにする必要がある。

HTML5モードを有効化するには、以下の3つが必要である。

(1) AngularJSのconfigでHTML5モードを有効化する

HTML5モードを有効化するには、$locationProviderを使用する。hashPrefixに’!’を設定することが、SEO上の観点からは推奨される。

(2) <base />タグ

index.html<head>の中に、href属性を含んだ<base />タグを追加する必要がある。これによって、ブラウザに、画像やCSS等の静的リソースをどこから取得すべきかを教える。

たとえば、アプリケーションがhttp://example.com/appというURLで提供されていてる場合、HTML5モードが有効になっていると、URLはhttp://example.com/app/user/1といった形になる。このとき、サーバはindex.htmlを返すが、ブラウザのパスは/app/user/1なので、相対パスではファイルを取得できない。以下のように設定されていれば、この問題を解決できる。

これによって、URLに関わらず、相対パスが/appを起点として解決されるようになる。

(3) サーバサイド

/first/page/second/pageといったルートに対するアクセスの場合にも、index.htmlが返されるようにする必要がある。

以下はNodeJSの場合の実装例。

AngularJSとSEO

検索エンジンについて気に留めておくべき点は以下のとおり。

  • GoogleやBingのような検索エンジンは、SPAに対応したパターンを有している。これらは、hashbangを使用したURLの使用を前提にしている(#ではなく、#!を使用したURL)
  • 検索エンジンがクロールする際、#!?escaped_fragment=に置き換えて、サーバにリクエストを送る

さらに、サーバーが検索エンジンからのリクエストを判別して、以下のような手段を使用してレスポンスを返す必要がある。

  • HTMLのスナップショットを作成して返す: AngularJSのドキュメントはこの方法を使用している
  • HTMLをリアルタイムにレンダリングして返す: PhantomJSのようなヘッドレスブラウザをサーバで使用してHTMLをレンダリングする

angular-seoのようなツールの使用も検討すべきである。

AngularJSと分析サービス

Google Analyticsのような分析サービスは、SPAでは簡単には動かない。分析サービスは、ページの読み込み時のイベントにフックして処理を行っているからである。SPAでは、Google Analyticsを手動で呼び出してルートの変更を教える必要がある。これには、Angularyticsのようなライブラリが使用できる。

代替となるルーター

ngRouteで、70-80%のニーズは満たすことができる。SPAのURLは一般的にシンプルなものになるからである。

しかし、URLに複雑な要求があったり、URLに応じてUIを細かに制御したいような場合には、ngRouteの機能では対応が難しい。

このような場合には、ui-routerの使用を検討すべきである。

※2016年3月現在では、New Routerも代替候補の1つになる。

『Java言語で学ぶデザインパターン入門』をPHPで実習する 第7章Builder

増補改訂版Java言語で学ぶデザインパターン入門

本記事に掲載したサンプルコードは、https://github.com/ryo-utsunomiya/design_patternでも公開中です。

Builderとは

Builderとは、構造を持ったインスタンスを組み上げるパターンです。

ここでは、文書を構成するためのBuilderクラスと、Builderクラスを使って文書を組み上げるDirectoryクラスを実装します。

まず、Builderの挙動を定めたインタフェースを作成します。

次に、Directorを実装します。Directoryは、Builderインタフェースのメソッドだけを実行します(「抽象に依存せよ」)。

これで、「文書を構成する仕組みの骨組み」と、「文書を実際に作成する処理」が出来ました。次に、実際に文書を作成する処理を実装します。

ここで実装した、TextBuilderクラスは、単純な文字列を組み立てるクラスです。Builderはインタフェースが定義されているので、必要に応じて、HtmlBuilderなどのクラスを実装することもできます。

TextBuilderを使用したコードは以下のようになります。

『Java言語で学ぶデザインパターン入門』をPHPで実習する 第6章Prototype

増補改訂版Java言語で学ぶデザインパターン入門

本記事に掲載したサンプルコードは、https://github.com/ryo-utsunomiya/design_patternでも公開中です。

Prototypeとは

クラスからインスタンスを生成するのではなく、インスタンスから別のインスタンスを作り出すことを、Prototypeパターンと呼びます。

Prototypeパターンの例として、Productという文字列を表示するインタフェースと、Productインスタンスの生成及び管理を行うManagerというクラスが存在するとします。

次に、Productインタフェースを実装したUnderlinePenクラスを作成します。このクラスは、与えられた文字列を使用して下線を引きます。

以下が、これらのクラスの使用例です。

これを実行すると、以下のように表示されます。

このコードだとありがたみが分かりづらいですが、UnderlinePenのインスタンスを生成するのが難しい場合などには、一度インスタンスを生成しておいて、後で再利用できるようになっていると便利そうですね。

『Java言語で学ぶデザインパターン入門』をPHPで実習する 第5章Singleton

増補改訂版Java言語で学ぶデザインパターン入門

本記事に掲載したサンプルコードは、https://github.com/ryo-utsunomiya/design_patternでも公開中です。

Singletonとは

Singletonは、インスタンスが1個しか存在しないことを保証するパターンです。

Singletonでは、newによるインスタンスの生成を禁止するため、コンストラクタをprivateにするのが一般的です。その代わり、インスタンスの生成・取得を担うメソッドを用意します。

上記サンプルコードのSingletonクラスでは、getInstanceメソッドがインスタンスの生成・取得を担っています。Singletonクラスのインスタンスがself::$singletonにセットされていればそれを返し、セットされていない場合はインスタンスを生成して返します。

このクラスを利用したコードは以下のようになります。

$obj1と$obj2は別の変数ですが、全く同一のインスタンスを参照しています。

なお、PHPではオブジェクトの比較には注意が必要です。

比較演算子(==)を使用する際、 オブジェクト変数は、単純に比較されます。つまり、 二つのオブジェクトのインスタンスは、 同じ属性と値を有し、同じクラスのインスタンスである場合に、 等しいとされます。

一致演算子(===)を使用する場合、 オブジェクト変数は、同じクラスの同じインスタンスを参照する場合のみ、 等しいとされます。

http://php.net/manual/ja/language.oop5.object-comparison.php

ここでは、同じインスタンスであることを確認するため「===」を使用しています。