第23回 自分用デザインパターン まとめ
前提
継承を用いて、親クラスの機能を子クラスが拡張することによる拡張を「静的な拡張」、実行時に new などによって作られたインスタンスを受け取りそのインスタンスを使って機能拡張する拡張を「動的な拡張」と呼ぶ。
静的: class 親{ do{} } class 子 extends 親 { do{ //前処理; super.do(); //後処理; } } 動的: class 元{ do{} } class 拡張{ コンストラクタ{ moto = 引数にもらった元; } do{ //前処理; moto.do(); //後処理; } } class Main{ main{ sugoi_moto = new 拡張( new 元() ); } }
参考:
- 作者: 下岡秀幸,道端良,畑勝也
- 出版社/メーカー: 秀和システム
- 発売日: 2006/11/27
- メディア: 単行本
- 購入: 4人 クリック: 39回
- この商品を含むブログ (33件) を見る
Template Method
子クラスで、親クラスのメソッドを実装(静的な拡張)。
子クラスでの共通処理は親のメソッドですべて実装済みにしておき、
別々の処理だけを穴埋めに式にして子クラスに実装してもらう。
class 親 { template_method { //子クラスで実装; } do { //前処理; template_method(); //後処理; } } class 子 extends 親 { template_method { //何か処理; } } class Main { main { new 子.do(); } }
Factory Method
Template Methodでインスタンス生成だけのメソッドを用意するイメージ。
class 親 { factory_method { //子クラスで実装; } do { something_instance = factory_method(); //前処理; somethinb_instance.hoge(); // ポリモーフィズムによる実行 //後処理; } class 子 extends 親 { factory_method{ return new Hoge(); } } class Main{ main{ new 子().do(); } }
document.createElement("table");
のようなものは、インスタンスを生成するメソッドではあるが、
サブクラスが関係していないのでFactory Methodパターンとは言いがたい。
しかし これは、GoFがサブクラスによるあくまで拡張の話を述べられているという点に関してFactory Methodと違うという意味であり、 document.createElement() のような使い方をFactory Methodと呼ぶ人も多い。
Strategy
動的に処理を入れ替える方法。関数がファーストオブジェクトな言語では無意識に使われる。
class 親 { strategy{ //子クラスで実装; } } class 子1 extends 親 { strategy { //戦略1; } } class 子2 extends 親 { strategy { //戦略2; } } class Main{ main { if( 敵強い ){ Execute = new 子1(); } else { Execute = new 子2(); } Execute.strategy(); } }
Decorator
あるインスタンスに対し、動的に付加機能を追加する。
これを静的にやろうとするとオーバーライドで行う。
動的にやるということに名前をつけたものなので動的であることが重要。
動的: class 元 { execute { //何か処理; } } class 修飾 { コンストラクタ{ Moto = コンストラクタ呼び出し時にもらった元; } execute { //前処理; Moto.execute(); //後処理; } } class Main { main { Syuusyoku = new 修飾( new 元(); ); Syuusyoku.execute(); } } } 静的: class 親 { execute { //何か処理; } } class 子 extends 親 { execute { //前処理; super.execute; //後処理; } } class Main { main { new 子().execute(); } }
Builder
なにかを作るにあたって順序がきまっており、その部品の作成に手間がかかる場合、
「順序(監督)」の部分と「部品生成(ビルダー)」の部分をわけて実装すること。
コンクリートの土台 → 粘土の壁 → わらの屋根
class 家 { dodai; kabe; yane; //部品生成者(ビルダー)に代入してもらう } class わらの屋根の家を作る人 { house = new 家(); make_dodai { house.dodai = コンクリート; } make_kabe { house.kabe = 粘土; } make_yane { house.yane = わら; } get_house { return house; } } class 監督 { make_house { builder = new わらの家を作る人(); builder.make_dodai(); builder.make_kabe(); builder.make_yane(); return builder.get_house(); } } class Main { main { new 監督().get_house(); } }
Abstract Factory
Factory Methodは子クラスでインスタンスを返すメソッドを実装する話であったが、
Abstract Factoryはインスタンスを返す工場クラスの話。
インスタンスを返すというところはFactory Methodと同じであるが、やりたいこと(目的)はぜんぜん違う。
Factory Methodは処理の穴埋め的な要素が強いが、Abstract Factoryは返したいインスタンスが状況により違っており、また生成するインスタンスが複雑な場合に用いられる。このとき生成するインスタンスごとに工場を用意する。Builderパターンと似ているところがある。
class Factoryの親 { create_instance { // 子クラスで実装 } } //ダックタイピングがないので仕方なく class Hogeを返すFactory extends Factoryの親 { create_instance { return new Hoge(); } } class Fooを返すFactory extends Factoryの親 { create_instance { return new Foo(); } } class Main { main{ if( Hoge ) { factory = new Hogeを返すFactory(); } else if( Foo ){ factory = new Fooを返すFactory(); } something_instance = factory.create_instance(); something_instance.do(); } }
something_instanceの部分もどんなインスタンスが返ってこようが同名のメソッドを呼ぶことになるのでポリモーフィズムを使うが、静的型付けな言語ではこのsomething_instanceにダックタイピングが使えないので、生成されるインスタンスの親クラスを作らないといけないため、このAbstract Factoryの説明に登場するクラスがかなり多く感じられる説明が多い。
参考:
http://www6.ocn.ne.jp/~duck/technics/gof95/abstractFactory.html
http://634.ayumu-baby.com/gof_designpattern/design_abstractfactory.html
Abstract Factoryパターン と Builderパターンの違い
Singleton
インスタンスをひとつしか作らせないための仕組み
Flyweight
プログラムを「軽量化」させるのが目的。
Flyweightパターンが軽量化させるものはインスタンスの生成。
複数箇所で同一のインスタンスが使われる場合、同一のインスタンスを使わせる。
同一のインスタンスを生成させる方法としてハッシュなどにインスタンスを登録していき、使う際はそのハッシュから取り出すといったことを行う。
またこのハッシュを持つクラスを用意し、それをFactoryと呼ぶこととしている。
こうなってくるとハッシュだけでよいのではないか? Factory余計では? と思えるが、複数のクラスからこのハッシュを使いたい場合、何かしらのクラス(ここではFactory)がないと、ハッシュにアクセスしずらくなるのもの事実。
class Factory { ハッシュ = new Hash(); get_instance { if( ハッシュにない ) { hoge = new Hoge('引数の値'); ハッシュ.set( '引数の値', hoge); return hoge; } else if(ハッシュにある) { return ハッシュから取得したもの } } } class Main { main { factory = new Factory(); hoge1 = factory.get_instance('あ'); hoge2 = factory.get_instance('い'); hoge3 = factory.get_instance('あ'); // hoge1と同じものを取得 }
なお、Factoryをシングルトンとし複数のハッシュが作られないようにすることが多い。
State
状態に応じて処理をわける。つまり、状態ごとにクラスを作るパターン。
関数型言語のディスパッチャーのような感じ。
Perl,JSなどでは、ハッシュに関数をどんどん登録していけばよいが、Javaではクラス必須なので、ハッシュに相当する役目のクラスはContextと呼び、そいつにStateなクラスを保持し、Stateなクラスのメソッド呼ぶ。なお、StateクラスのメソッドにContextを与え、StateクラスでContextの状態を変更するというような、ソースを追うときにあっちこっちに目を通さないといけない実装になっているものもある。この場合、Stateの中でContextの状態が書き変わるので、副作用ありまくりすぎてソースが読みづらい。
Bridge
「機能の拡張」と「実装の拡張」の橋渡しをするパターン。
継承には「機能の拡張(メソッドの追加)」と「実装の拡張(Template Methodパターンに代表されるもの)」がある。
「機能の拡張」と「実装の拡張」をひとつの子クラスで同時に行うと保守しずらいという意見から、「機能の拡張」は機能の拡張をするだけの子クラス、「実装の拡張」は実装の拡張をするだけの子クラスというように設計しようということにすると、本当に使いたいクラスは1つなのに2つできあがってしまうことになる。そこで、どちらかのクラスにもうひとつのクラスを持たせることで、使用する側はひとつのクラスしか意識しないでいいようにしようとしたパターンである。
class Main{ main{ 欲しいクラス = new 機能の拡張( new 実装の拡張() ); 欲しいクラス.メソッド(); } }
このように「機能の拡張」したクラスに「実装の拡張」のインスタンスをコンストラクタで渡し、それを保持させる。機能の拡張は内部で、実装の拡張のメソッドを呼び出すことをしている。
欲しいクラスの型は結局、機能の拡張と同じである。機能の拡張クラスが実装の拡張のメソッドを呼んでいるところが橋渡しをしているようなのでBridgeと呼ばれる所以である。
参考:
- 作者: 結城浩
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2004/06/19
- メディア: 大型本
- 購入: 51人 クリック: 762回
- この商品を含むブログ (397件) を見る
Adapter
java.util.loggingパッケージやLog4Jなどロギングシステムがたくさんある。
これらはメソッドの呼び出し方などが違うため、ログシステムを他のものに変えたいときなどに、メソッドの大幅な書き換えが生じる。そこで、共通のインタフェースを持ったクラスを作成する。
参考:
- 作者: WINGSプロジェクト佐藤匡剛,山田祥寛
- 出版社/メーカー: 技術評論社
- 発売日: 2006/11/29
- メディア: 大型本
- 購入: 17人 クリック: 227回
- この商品を含むブログ (58件) を見る
Memento
オブジェクトの状態を昔の状態に戻したいときに使われるパターン。
ならば、普通にコピーして、Listかなにかにどんどん追加していけばよいのではと思うが、このパターンはオブジェクトのすべてを保存するわけではなく、保存したいものをあらかじめ選んでおく方法が取られている。
Originalクラスは、オリジナルのクラスである。つまり状態が保存されるクラスである。
Mementoクラスは、オリジナルの状態を保存するクラスである。このクラスにOriginalクラスの中から残しておきたいフィールドだけを書いておく。つまり、MementoクラスにOriginalの状態を保存していく。MementoクラスとOriginalクラスの対応は1対1になる。
Caretakerクラスは、Mementoクラスを内部のListなどに追加していくクラスである。状態を復元したいときこのCaretakerクラスからMementoを取得する。
なお、OriginalクラスのメソッドからMementoクラスを取得し、復元したいときはOriginalクラスが復元したい状態のMementoクラスをCarertakerクラスからもらうという複雑な構造をとる。これは、OriginalクラスのみがOriginalを変更(復元)したいという思想からできている。
参考:
矢沢久雄の早わかりGoFデザインパターン(最終回) | 日経 xTECH(クロステック)
http://www.rarestyle.net/main/patterns/memento.aspx
Memento パターン - Wikipedia
Facade
「正面玄関」の意。
複数のクラスを使うとき、Facadeクラスから各クラスを呼び出し、使い手は各クラスを意識することなく、Facadeクラスを使えばよいというパターン。
本来使いたいクラスとの間にFacadeを設けることにより「疎結合」にしたい意味合がある。
また、処理の手順が複雑になってしまう場合もFacadeクラスがそれを担うことができる。
Builderパターンも同じような感じだが、Builderはインスタンスの生成に重きを置いているが、Facadeは処理の手順や、Facadeがクラスを使う人に簡単なAPIを提供することで、複数のクラスの関係や処理手順をあまり意識しないで良いようにするという意味合が強いように思える。
Proxy
いろいろなクラスの代理となるクラスを設けるパターン。
直接、目的のクラスにアクセスさせたくない場合、Proxyクラスを介してアクセスさせるというように使われる。このように「権限」を制御したい場合によく使われるパターン。
Composite
木構造をあらわすクラス。
枝の役割のクラスと葉の役割のクラスを持つ。
枝と葉は共通名前のメソッドを持っており、同様に扱える。もちろん枝は葉を持つためにaddメソッドを持っていたいするが葉は持っていないため枝のほうがメソッドの数は多くなる。
Javaのような静的型付け言語では葉と枝用のインタフェースなどを作ってポリモーフィズムしてやらないといけない。
参考:
http://ja.wikipedia.org/wiki/Composite_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3:titel
Visitor
オーバーロード
引数の型が違う同名のメソッドを用意する。早い話がオーバーロードである。
オーバーロードを使えば、呼ばれるメソッドを実行時に決めることができる(もらう型によって呼ばれるメソッドが違うため)
ダブルディスパッチ
class Hoge{ sub accept(Foo f){ f.visit(this) } } class Main{ sub main(){ f = new Foo(); h = new Hoge(); hoge.accept(f); } }
というように、もらった引数にthis(自分自身)を渡して処理を呼び出す形をダブルディスパッチという。
Visitorパターンの目的
Visitorパターンの用途は「データ構造」と「処理」の分離にある。
また別の用途としては、クラスに処理への処理の追加などもある。
データ構造となるクラスに acceptメソッド(Visitorを受け入れるメソッド)を用意しておき、これにそのデータを処理するメソッドを持ったVisitorを受け取る。Visitorはダブルディスパッチによりデータ構造をもらっておりこのデータ構造をもらって処理を行う。このときVisitorは同名のメソッドを複数持っている。早い話がオーバーロードである。これによりVisitorはいろいろなデータ構造に対する処理だけが詰まったクラスとして製作することができる。
イメージ的にはRubyのArray.eachのようにデータ構造に対する処理をもらう場合、クラス必須の言語ではダブルディスパッチによりthisを持っていくことで、データ構造への処理を適用するという書き方(イディオム)がよく行われるというように捉えるとわかりやすいように思える。
Visitorパターンの重要なポイントは、オーバーロードによりデータ構造ごとの処理をわけれることにあると思える。
Prototype
オブジェクトの複製をつくることパターンのこと。
Perlで書くと、
$c = [@$hoge]; $d = {%$foo};
ということと思われる。
Javaだと、java.lang.Cloneableを継承して cloneメソッドをオーバーライドすれば実現できるようだ。
参考:
http://www.syboos.jp/sysdesign/doc/20080610172337964.html
Observer
ObserverとSubjectが出てくる。
SubjectはObserverを複数知っている。
Observerは1つのSubjectを知っている。
S 1 対 多 O
という関係。
class Subject { notify(){ for o in observers { o->update(this); } } } class Observer { update(chenged_subject){ if( chenged_subject == s ) { s->method(); } } }
まとめ
お互いがお互いのメソッドを呼ぶので少しややこしい。
Subjectが複数のObserverに変化があったことを教えると
Observerがその変化があったSubjectのメソッドを呼ぶ。
「変化があったことを教える(notifyする)とSubjectのメソッドを呼ぶ」
と表現すると、なんか魔法みたい!notifyどうなってるの!?
って感じだけど、notifyの中で、Observerのupdateメソッドを呼んでいる(o->updateって自力で書いている)ので
特に驚くことはない。
問題なのは、なんかしらの方法で、Subjectに複数のObserverの登録をしなきゃいけないし、
Observer側も自分が管理する1つのSubjectを登録しないといけないので、この登録処理のところの方が、
なにしてんだここって感じのソースになりやすい。(今回のソースでは登録部分は省略しました)
とまあ色々書いたけど、変更があったことを複数が知りたい時に、
うまくやりたいパターン。
Command
Objective-Cのターゲット・アクション機構。
行ないたい処理(アクション)とアクションに必要なデータをクラスとして扱うパターン。
Objective-C的発想ではなく単純にこのパターンで作っていくと、
実際に処理を行なう「Receiverクラス」のラッパーを「Commandクラス」として作りまくっていくことになる。
参考:
http://www.doyouphp.jp/phpdp/phpdp_02-3-5_command.shtml
ダイナミックObjective-C(84) デザインパターンをObjective-Cで - Command (1) | マイナビニュース
ダイナミックObjective-C(85) デザインパターンをObjective-Cで - Command (2) | マイナビニュース
ダイナミックObjective-C(86) デザインパターンをObjective-Cで - Command (3) | マイナビニュース
Mediator
Mediator「仲介者」を挟むことで、各クラスの結合を低めようとするパターン。
Objective-Cの「MVC」の「C」で使われているパターンである。
一瞬、Observerパターンと混乱したが、
Observerパターンは「変化があったことを、それを知りたいすべてのオブジェクトに知らせて何かしらの処理を行なってもらう」のが目的であり、
Mediatorは「疎結合」を目的としている。
参考:
ダイナミックObjective-C(112) デザインパターンをObjective-Cで - Mediator (1) | マイナビニュース
ダイナミックObjective-C(113) デザインパターンをObjective-Cで - Mediator (2) | マイナビニュース
ダイナミックObjective-C(114) デザインパターンをObjective-Cで - Mediator (3) | マイナビニュース