頭で把握するには23個は多すぎるので、関連や違いをすっきり整理したいところです。
2つの記事が面白かったのでので、適当に自分用に抜き書きしたものです。時間を作って、自分なりに整理したいと思います。
本が出版されたのは1994年であり、Java(1995)が出てくるよりも前だった
オブジェクト指向が未成熟な時代にカタログ化された
現代のプログラミングと合致しないものが多い
23あるデザインパターンの中で最も重要なのは下記の5つだ。 としている。
- Template Method
- Factory Method
- Composite
- State/Strategy
Template Method: 子が処理断片を具体化。Javaで抽象クラスを作る目的そのもの。
非常によく使うが、単なる
abstract
メソッドの使い方である。Builder : 初期化手順を細分化1個ずつ言い渡せるので,おかげでコンストラクタ内の初期化処理に何もかもを詰め込まないで済む。いろいろ属性を設定させて,最後に画面表示とか,よくある。
Adapter : 継承でラッパー/委譲でラッパー。既存APIを自分が好きなやり方でそれらのAPIを利用できるように,一段かませて「便利クラス」を作る。元のクラスを継承してクラス単位でwrapするか,元のクラスのインスタンスをコンポジションで保持してオブジェクト単位でwrapするか,の2パターンがある。
インタフェースをそろえるためにラッパーを用意することはよくある。
普通のOOPの使い方である。
Strategy: アルゴリズム切り替え。アルゴリズム実装のための専用オブジェクトを複数作っておき,その中から使うものだけを動的に選んで実行する。アルゴリズムの交換可能性を増すために,共通の基底(または共通のインタフェース)を持たせている。
単なる関数オブジェクトである。
Decorator : 委譲で意図的フック。DecoratorはDecorateeを引数に取るなどして外側をくるむ。操作対象にしたいオブジェクトをコンポジションで保持し,そいつにあれこれ操作を加える(Decorateする)。 Proxyパターンと酷似。
「委譲による継承」としてまとめるべきである。
Facade : 複数クラス利用手順書。複数のクラスを呼び出す際,呼び出す順番を思い出さなくてすむように,定石として1メソッド内に集約しておく。低レイヤのコードをいじらなくて済むよう,ひとまとまりの手順としてまとめておく。なおfacadeパターンには,「いつも同じ窓口を通過させることにより,共通処理を全体に埋め込みやすくする。」という使い方もある。
Mediator : スター状の相互作用。網目状の複雑な相互作用ではなく,中央にコントロールを集約した「スター状の結合」を介して,オブジェクト同士が相互作用する。中央にいるのは,管理者オブジェクトのようなもの。このUIはある程度複雑だな,と感づいたら,そのUI専用のマネージャクラスを立てて,各UI部品はそのマネージャクラスに対して通信するだけにする。ただし,このパターンは「中央集権的」な発想であり,余りにも複雑すぎるUIになってくると中央で管理しきれなくなって,限界がくる。そういう場合は,後述するChain of Responsibilityパターンで,中央集約ではなく分散構造/バブリングの発想に切り替える。
クラス間の疎結合を満たすために仲介クラスを用意するのは良い設計である。
有名なパターン
Factory Method: 動的にサブクラス選択。newキーワードを隠ぺいして何かのインスタンスを動的に生成して返していたら,そのメソッドはファクトリ・メソッドと呼ばれうる。Factoryは性質の異なる子クラスを増やす方針なわけだが,ジェネリクスはその真逆であり,たくさんあるクラスを型パラメータで1つに統一しようとする。なので,この2つの方針がぶつかりあうと,どこかで一斉にコードの矛盾が生じる。Factory等を始めとする「ポリモーフィズムをフルに生かして透過的に扱う」という設計パターンと,「型パラメータで抽象化して透過的に扱う」という設計戦略とが,ほぼ対極に位置するため相性が悪い。という点を覚えておいた方がいい。
Singleton : 1インスタンスを保証。シングルトンには落とし穴が結構あるので,よくわかっている人なら,このパターンの利用を避けるケースが多いと思う。
「状態を持たず多態性を利用したい」という状況でのみ使うべきである。多態性を利用しないのであれば、
static
を利用したほうが単純性が増す。 Observer: イベントリスナ。Observerという名前ではなく,Listenerという形で,知らないうちに使いこんでいるはず。監視対象にイベント観察者を埋め込んで,イベントが起こる時に何かさせる。
Proxy: こっそりフック。メインな処理をするオブジェクトをコンポジションで保持し,メインな処理の前後にフックをかませる。しかも,保持対象と保持者で共通のインタフェースを持っているために,相互の入れ替えがきくという点は,Decoratorパターンと全く同じDecoratorは意図的明示的にフックしていたが,Proxyの場合は影で,隠れて,こそこそとフックしている点が異なる。
直接触る代わりに、中間層を置くのは悪くない手法である。
汎用的過ぎて、デザインパターンと言えるのかは微妙。
Iterator: 並んだ物を順番に処理。あって当たり前のプログラミング・テクニック。言語によっては標準APIとして提供される。ちなみにRubyのような純粋なオブジェクト指向の言語であれば,モデル層の記述はイテレータ(each系のメソッド)尽くしのメソッドチェインになったりする。findしてmapしてselectしてmapしてrejectしてuniqしてconcatしてjoin(またはinject)とか,極めて生産的かつ「思った通りに書けば思った通りに動く」を実感する。
コロンブスの卵のパターン
State: 状態オブジェクト。Stateパターンは,個々の状態を表すための専用オブジェクトを作る。なので,状態遷移図ベースのダイレクトなコーディングが可能になる。いわば,Object-状態遷移図マッピングである。そういう経緯もあり,Strategyなんかと比べるとはるかに,システム設計の観点からして望ましいパターン。
OOPの良くないパターンである。状態遷移図があるような遷移を各クラスで表現すると、凝集度が下がりコードが読みづらい。普通に
state
フィールドとif
で書いたほうがわかりやすい。 Command: タスクキューとスタック。Commandでは1タスク=1オブジェクトとなる。共通のインタフェースを実装しているので,複数のタスクを同種のものとしてまとめて取り扱って管理できる。終わったタスクをスタック構造で保持すれば,「作業履歴」として参照可能になる。逆に,処理待ちのタスクはキュー構造で保持する。そういうわけで,インストーラのプログレスバーとか,アンドゥ・リドゥは,こうやって実装されていたのか!と開眼するきっかけになる。
このパターンで複数のことを説明しすぎである。まず、関数そのものをオブジェクトとし「切り替えて使える」ことは
Strategy
の説明に押し付けるべきであった。命令を抽象化しクラスにするのは良い設計である。例えばcanExecute
とexecute
を持つようなクラスを用意することは凝集度が高まってよい。 Memento: 状態のゲッタとセッタ。オブジェクトの状態のバックアップと復元が可能になり,アンドゥみたいなことができるようになる。バックアップ対象のオブジェクトには,そのオブジェクトの状態をバックアップするための専用のMementoオブジェクトを生成可能にする(状態のgetter)。特定のオブジェクトの「スナップショット」を取得&適用可能にする,という発想は,自然には生まれづらい。
Bridge: 拡張と実装の階層分離。「機能仕様書の進歩」と,「サポート環境の種類(タイプ)の多さ」という2つの軸を見抜くことがキーになる。Bridgeパターンを使えば,これら2つの軸を「タテ広がり」と「ヨコ広がり」に分解して整理できるのだ。とはいえ,このパターンが必要になる理由は,ただ単に「Java言語はコードの共有がしづらいプラットフォームだから」とも言える。
Composite: 再帰ツリー構造。ディレクトリ構造や組織の人員構成など,再帰的な構造を実装するために必須のパターン。ファイルとフォルダを同一視/抽象化して,両者に共通の性質(インタフェースまたは基底クラス)を持たせるところがポイント。この見方は,自然には生まれづらい。オブジェクト-再帰構造・マッピング。
Interpretor: 独自言語の実行。構文解析の結果を,オブジェクトにマッピングし,実行させるのである。Object-BNFマッピングとでも言うべきパターン。クラス構造が再帰的なのはCompositeと類似。
Chain of Responsibillity: 助け船ネットワーク。自分では処理しきれないタスクだ,と判断したら,自分が知っている助け舟にヘルプを求めて,そいつに任せてしまう。技術的な言葉を使って説明すれば,GUIシステムにおける「イベントのバブリング」である。例えばブラウザ上のDOM構造で,JavaScriptのイベントは,親ノードへと伝搬してゆく。
「委譲による継承」としてまとめるべきである。
Visitor: 構造の便利スキャナ。複雑なデータ構造がある場合,データ構造を表すオブジェクトの中には具体的な処理を書かない。かわりに,自分に対して処理を行なってくれるVisitorを受け入れるというロジックだけを整えておく。そして,Visitorはデータ構造を渡り歩き,各データに対して所定の処理を行なう。Visitorはデータ構造をスキャンしながら仕事をこなしているのである。ただ,ここまでやらなくても十分「仕事しながらスキャン」できるのでVisitorを実際に使う必要はない,という場合もある。
メリットがわかりずらいパターン
Prototype: コピーを渡す。派生オブジェクト達をあらかじめ生成して陳列しておいて,必要な時にコピーを渡す。浅いコピーのせいでトラブルが起きたら…?
Abstract Factory: 工場の工場。工場は,命令に応じて様々なプロダクトを動的に作る。でも,だんだん工場のバリエーションが増えてくる。そこで,工場自体を動的に生成して,さまざまな工場のバリエーションを持たせる。工場の工場。
Flyweight: キャッシュ付の工場。インスタンス生成の負荷を減らすために,既存のオブジェクトを使いまわす事によってパフォーマンスを向上させる。という点では,SingletonパターンやPrototypeパターンと同じ。Flyweightパターンの場合は実現すると「Factoryにキャッシュ機能が付いたような物」ができあがる。
http://language-and-engineering.hatenablog.jp/entry/20120330/p1