GoFの23パターンについて
原則
- 変わるものを変わらないものから分離する
- インターフェイスに対してプログラミングし、実装に対して行わない
- 継承より集約
- 委譲、委譲、委譲
- 必要になるまで作るな(You Ain’t Gonna Need It./YAGNI)
構造に関するパターン
Adapterパターン
すでに実装されているものと、必要な機能の差分を埋める。(拡張)
オブジェクトから別のオブジェクトに対するインターフェイスを提供する、あるいは他のオブジェクトとのインターフェイスの差異を吸収するためのラッパーオブジェクトが必要な場合。
継承
mix-in
クラスに変更をかけたくない場合はextendで機能拡張をする。
オープンクラスまたはRefinementsを用いる
Rubyではあらゆるクラスに対して拡張が可能なため、子クラスを用意するのではなくクラス自体を拡張することでAdapterパターンの目的(インターフェースのズレを埋める)を達成できる。
サンプルコード
Bridge パターン
抽象クラスと具象クラスそれぞれを独立させることで保守性を高める。
新たな機能、新たな実装の追加が容易となる
サンプルコード
Composite パターン
部分から全体を組み立てる。
全てのオブジェクトは共通の基底クラス(コンポーネント)を継承する。
コンポーネントを継承し、子オブジェクトを持つオブジェクトをコンポジットという。これにより任意の深さのツリー構造ができる。
コンポーネントを継承し、子オブジェクトを持たない末端のオブジェクトをリーフという。子オブジェクトを持たないコンポジットを末端のオブジェクトにしても良い。
大小様々な同じ振る舞いをもつオブジェクトに親子関係があり、ツリー構造を実装したい場合に用いる。
Decorator パターン
オブジェクトを改良する。
元のオブジェクトのプロキシーオブジェクトを基底クラスとし、追加機能を実装するオブジェクトに基底クラスを継承することで、元のオブジェクトを変更することなく機能を追加することができる。委譲はForwardableモジュールを使うことで簡単に実装できる。
Ruby特有の方法として、他に2津の方法がある。1つめは、ailasキーワードと特異メソッドを使うことで動的に機能を変更することができる。2つめは、moduleをextendして動的に機能を変更することができる。
元のオブジェクトを変更すること無く、オブジェクトに機能を追加したい場合。
元のオブジェクトを簡潔に保ちつつ、委譲により拡張機能を追加したい場合。
Facade パターン
アプリケーションを開発していると、「Aの処理とBの処理は必ずセットで実施しなければならない」というケースがあります。たとえば、「ユーザが退会する際には、併せてメールマガジンを送るのもストップする」といった感じです。
プログラマが全ての組み合わせを把握できていればよいのですが、万が一なにか1つを忘れてしまった場合、それは重大なバグにつながります。そのような事態を防ぐため、「一連の手順をセットで実行してくれるクラスやメソッド」を作って利用するのがFacadeパターンです。
// ※インポートやエラーハンドリングなどの記述は割愛
public class Facade {
public void secede(int user_id) {
// ユーザ退会
UserDao userDao = new UserDao();
userDao.secede(user_id);
// メールマガジンを送るのもストップ
MailMagazineDao mailMagazineDao = new MailMagazineDao();
mailMagazineDao.secede(user_id);
}
}
Facadeクラスのsecede(退会する、の意)メソッド内部では、さきほど説明した「ユーザが退会する」、「メールマガジンを送るのもストップする」という処理をセットにして実施しています。このため、他のクラスはFacadeクラスの持つsecedeメソッドを呼び出すだけでよくなり、より実装がシンプルになるのです。
Flyweight パターン
インスタンスを作れば作るほど、プログラムの動作は重たくなります。これはつまり、「無駄なインスタンスを生成しなければ、プログラムの動作が軽くなる」ということの裏返し。Flyweightパターンは、そのような目的のために使用されるパターンです。
Flyweightとは英語で「フライ級」を意味し、ボクシングなどの体重階級の中でもっとも軽い部類のこと。つまり、それだけ動作を軽量化できるパターンなのです。
// ※インポートやエラーハンドリングなどの記述は割愛
public class Flyweight {
Map<String, SomeClass> map = new HashMap<String, SomeClass>();
public synchronized SomeClass getSomeClass(String name) {
SomeClass someClass = map.get(name);
if (someClass == null) {
someClass = new SomeClass(name);
map.put(name, someClass);
}
return someClass;
}
}
FlyweightクラスのgetSomeClassメソッドの中では、「mapの中にその名前のインスタンスがふくまれているかどうか」のチェックをしています。
ふくまれている場合はそのインスタンスを返し、わざわざ新しく作り直すことはありません。これによって無駄なインスタンスの生成を防ぎ、動作を軽くすることができるのです。
Proxyパターン
オブジェクトに代理を立てる。
本来の処理をプロキシーでラッピングすることで関心事の分離を行いたい場合。
必要になるまでインスタンス生成を遅らせ、軽量な代理オブジェクト(Proxy)が処理を代行するパターン。
Rubyでは処理をProc化しておくことで遅延実行が可能。必要になるまで処理の実行を遅らせることができる。
代理オブジェクトを用意するより、重い処理を遅延実行したほうが良さそう。