事の起こり
最近、デザインパターンの勉強をしているのですが、説明が抽象的すぎて覚えずらいなと感じています。例えば、
デザインパターンは
- 生成に関するパターン
- 構造に関するパターン
- 振る舞いに関するパターン
の3つに分類される。
...。いや、抽象的すぎて全然、どのパターンがどの分類になるのか分からない。というか、分類が大雑把すぎて、自分のやりたいことに対してどのパターンを参考にすべきか分からない(泣)。
ということで、自分なりにデザインパターンを考察して、分類してみました。
個人的デザインパターンの活用場面分類
早速、本題です。私なりに次のようなデザインパターンの分類を考えてみました。なるべく、やりたいことに対してどのデザインパターンを使うのかという観点で分類しました。
分類 | デザインパターン名 |
---|---|
1.外部IFの設計時に参考にしたいデザインパターン | Adaptor、Proxy、Facade |
2.処理の大筋を変えずに変更・修正に柔軟に対応したい時のパターン | Template、Bridage、Strategy |
3.プログラムの拡張性を高めたい時に参考になるパターン | Obsever、Decorator、Visitor |
4.シングルトン(こいつは分類できないので例外) | Singleton |
5.複雑なインスタンス生成処理をクラス化したい時に参考になるパターン | Factory Method、Abstract Factory、Builder |
6.インスタンスの管理を上手に行いたい時のパターン | Flyweight、Prototype |
7.中央集権的にクラス管理をしたい時のパターン | Mediator |
8.クラスの状態を元に戻したりした時に参考にしたいパターン | Memento |
9.もはや言語仕様と化したもの | Iterator |
10.処理そのものをクラスとして部品化するパターン | Command、Chain of Responsibility |
11.状態をそのもをクラスとして表現するパターン | State |
12.データ構造をそのものをクラスで表現するパターン | Composite |
それぞの分類について
かなり大雑把ですが、デザインパターン使用の時に何を実装するかとそのクラスの雑な説明をまとめました。完全に個人の覚書なので参考程度に
1.外部IFの設計時に参考にしたいデザインパターン
パターン | 実装するもの | 概要 |
---|---|---|
Adaptor | 提供物を上手に使うラッパーをクラスにまとめる | ただのラッパー。ただ、最近は継承ではなく移譲で実装する。ラッパーを知らない人はすぐに調べよう |
Proxy |
Proxyクラス (1) subjectクラスのメンバ (2)コンストラクタ ただし、ここではsubjectクラスは生成しない (3) subjectクラスを生成して(1)にセットするメソッド (4) メソッド(3)でsubjectクラスを生成し、subjectクラスのメイン処理(6)を行うメソッド Subjectクラス (5)コンストラクタ (6)Subjectクラスのメイン処理 |
要するに実際に使う段階になってから、あるインスタンスを生成して、そのメイン処理を行ってあげるクラスをProxcyクラスという。初期化の段階で全部のインスタンスを生成すると処理的に重たいので使うときだけクラス生成しようという考えのデザインパターン。呼吸をするように至る所で使われているので、Proxyがデザインパターンだと認識していない人も多いと思います。 |
Facade | Facadeクラスというクラスの操作を専門とするクラスを実装する | これはデザインパターンなのか? 要は部品の操作をまとめたFacadeクラスを外部へのIFとして提供するというだけの考え。 |
2.処理の大筋を変えずに変更・修正に柔軟に対応したい時のパターン
パターン | 実装するもの | 概要 |
---|---|---|
Template Method |
Templateクラス (1)大きな処理の流れを記述したメソッド(実装) (2) (1)の流れを構成するメソッドのインターフェース Templateのサブクラス (3) (1)の流れを構成するメソッドの実装((2)の実装) |
実装を担当するプログラマに、処理の流れを伝える画期的な方法だと感じる。 |
Bridge |
機能クラス (1) 実装クラスのメンバ (2)実装クラスにある各メソッドのラッパ- (3)(2)のラッパーを利用して成される機能 実装クラス (4) (3)の機能を構成する各種メソッド |
機能のクラスと実装のクラスにという考えを学べる。聞いたことがない人は調べてみよう。実装のクラスで環境(OSなど)に対応し、機能のクラスは実装のクラスのメソッドを利用した機能だけを外部に提供するという考え。 |
Strategy |
Strategyを利用するクラス (1) Strategyクラスのメンバ (2) Strategyを引数にしたコンストラクタ(引数は(1)にセット) (3) (1)のStrategyのメソッドを利用したメイン処理 Strategyクラス (4) (3)の流れを構成するメソッド |
デザインパターンの最有能。 (1) 状況毎に対応したStrategyをいくつも作る (2)インスタンス生成の際に渡されたStrategyによりメイン処理の大筋を保証しつつも変更に対応するという考えは非常に有益! まあ、C++だったらテンプレート、pythonならダックタイピング、C言語なら関数ポインタを使えば代用できるので影は薄い |
3.プログラムの拡張性を高めたい時に参考になるパターン
パターン | 実装するもの | 概要 |
---|---|---|
Observer |
[Observerクラス] (1) 「観察される側のクラスを引数」とし、そのメソッドを実行しすることでupdataを行うメソッド [観察される側のクラス] (2)自身を観察するObserver達を管理するコンテナ (3)(2)に新たなObserverを追加する処理 (4)(2)の新たなObserverを削除する処理 (5) (2)のObserver全てに通知(Notify)を行う処理。通知の際は自身(this)を引数として(1)に渡す |
観察される側がObseverを管理するというマゾな設計だと思う。Mediatorと似ているがMediatorは管理する側と管理される側がお互をメンバとして持ち合っているが、Obseverパターンは管理される側が管理する側(Observer)を一方的にメンバとして持っている。機能拡張をしたい場合は、その拡張機能を持つ新たなObserverクラスを作って、観察される側のクラスに登録するだけでいい。 |
Decorator |
親クラス (1) 機能拡張したいメソッドを定義 子クラス (2)親クラスのメンバ変数 (3)コンストラクタ (4)(1)のメソッドをオーバーライドしたメソッド、このクラスで(2)の親クラスのメソッド(1)を実行する |
子クラスが継承している親クラスをメンバとして持つ(移譲)のが特徴。このような子クラスのサブクラスを追加することで(1)のメソッドの機能拡張を図る |
Visitor |
Visitorクラス (1)データを訪問した時に行う実際の処理visitメソッド Visitorに訪問されるデータクラス (2) 引数にVisitorクラスを受取り(1)のvisitメソッドを実行するacceptメソッド |
データとそれに対する振舞いをそれぞれクラスで分ける発想。データ構造の変更せずに機能拡張すること最大の目的。Visitorパターンに出会ったら、データ構造には手は加えないようにしたい。 |
4.シングルトン
パターン | 実装するもの | 概要 |
---|---|---|
Singleton | (1) private修飾子をつけたコンストラクタ (2) (1)のコンストラクタで自身のインスタンスを生成し返すstaticメソッド |
クラスのコンストラクタにprivateを着けて、生成を制限すると言うやり方は意外と思いつかない。割と感動しました。でも嫌われている。 |
5.複雑なインスタンス生成処理をクラス化したい時に参考になるパターン
パターン | 実装するもの | 概要 |
---|---|---|
Factory Method |
Factory クラス (1) インスタンス生成の処理の流れを記述したメソッド (2)(1)を流れを構成するメソッドのインターフェース。返値は何かしらのインスタンスにする。 Factoryのサブクラス (3) (1)の流れを構成するメソッド(2)の実装。返値は何かのインスタンス。 |
何よりも、Factory methodパターン≠ Factoryパターンであるということが大事。条件分岐で生成するインスタンスを変更するのがFactoryパターンで、Factoryメソッドはインスタンス生成用のクラスと生成されるクラスを一対一で生成するパターンのこと。インスタンス生成が複雑化してくるとFactory methodのFactoryインスタンスをFactoryパターンで分岐させ生成するということもするが、ある程度実績のあるライブラリなどでしか見かけないので稀有な事例だと思う。 |
Abstract Factory | 割愛 | **最初からこんながっつり抽象クラス設計するのは無理だろ。。。![]() |
Bulder |
Bulderを利用するクラス (1)Builderクラスのメンバ (2)Builderクラスを引数にするコンストラクタ (3)Builderクラスのメソッドを利用し何か(この何かは引数で与えられたBulderにより変化)を構築するメソッド Builderクラス (4) (3)の構築に関係するメソッドを実装する |
デザインパターンの一つであるStrategy と同じ考え。strategyのインスタンス生成ver。 |
6.インスタンスの管理を上手に行いたい時のパターン
パターン | 実装するもの | 概要 |
---|---|---|
FlyWeight |
インスタンス管理クラス (1)privateコンストラクタ (2)自身を生成し返すクラス (2)インスタンスを管理するハッシュテーブル (3) 引数で指定したインスタンスを返すstaticメソッド。 指定されたインスタンスが(2)のテーブルに存在する場合はそれを返す。存在しない場合はインスタンスを生成し ハッシュテーブル(2)に登録する。 管理される側のクラス (4)コンストラクタ。ただし、「インスタンス管理クラス」のメソッド(3)を呼び出す。 |
インスタンスを共有してメモリ節約という考え方。しかし、「共有していい情報」をどう選出するかがかなり難しいところ。Protoypeはコピーを返すが、Flyweightは全く同じものを共有する。インスタンス管理クラスはメモリという実体のあるものを管理するので、いくつもあっては困る。なので、シングルトンで実装する |
Prototype |
Productクラス (1)自身を複製(クローン)するメソッド Productを複製するクラス (2) (1)のProductクラスを登録するハッシュテーブル (3) 引数で受けたProductクラスをハッシュテーブル(2)に登録するクラス (4)引数のハッシュ値から対応するProductを取出し**(1)のメソッドで複製して**返すクラス |
インスタンスの**複製(クローン)**をいくつも作りたい時に効果的なデザイン。要はインスタンスのコピー&ペーストの実装方法。Flyweightと違い新規作成 |
7.中央集権的にクラス管理をしたい時のパターン
パターン | 実装するもの | 概要 |
---|---|---|
Mediator |
Mediatorクラス(相談役) (1) 相談するクラス全てをメンバとして持つ (2) 相談を受けた時の処理を記述したメソッド((5)を呼出して相談するクラスへの結果通知まで行う) 相談するクラス (3) Mediatorクラスをメンバとして持つ (4) Mediatorを受け取って(3)として保持するメソッド (4) Mediatorに相談するメソッド((2)を実行するラッパーとして実装) (5) Mediatorからの相談結果を受け取るクラス、(2)の返値を引数にする |
システムを構成するクラスの振舞いをクラス自身に実装するのではなく、Mediatorというクラスに集約するという考え。 各部品が同じ条件に対して振舞いを変える時はMedatorにその条件と対応する処理を集めて、プログラムを簡潔にする。ただ、設計の難易度は高めだと思うので、リファクタリングの際に使用したい |
8.クラスの状態を元に戻したりした時に参考にしたいパターン
パターン | 実装するもの | 概要 |
---|---|---|
Memento |
Originatorクラス (1)現時点の状態をMementクラスのインスタンスとして生成し、返すメソッド (2)Memntoクラスを引数として、その状態を自身の状態(メンバ変数)にセットするメソッド(undoメソッド) Mementoクラス (3)Originateクラスと共通するメンバ変数を持たせる |
Originatorクラスの状態をMementoクラスとして保存する。Originatorクラスの状態を変更(元に戻す)などしたい時は、戻したい時点でのMementoインスタンスを渡して設定((2)メソッドを実行すれば)すれば良い。 MementoはOriginatorの状態を表すので両者のメンバ変数などはある程度共通させることがポイント。 |
9もはや言語仕様と化したもの
パターン | 実装するもの | 概要 |
---|---|---|
Iterator | (1) 順番並んだデータ (2)(1)の一点を指しすindex (3) (1)のデータに次の要素があるか調べるメソッド (4)(1)現在のindexが指すデータを返し、indexを進めるメソッド |
順序があるデータのシーケンスの為にIteratorを実装する。どの言語にも標準機能として備わるほど有名。 |
10.処理そのものをクラスとして部品化するパターン
パターン | 実装するもの | 概要 |
---|---|---|
Command |
Commandクラス (1)自身を実行するexecuteメソッド |
処理自体をクラスとするという考え。Commandクラスをコンテナに格納して、イテレータで順番にexecuteしていくという使い方もできる。コンテナの情報を丸ごと出力させればCommand実行履歴にもなる。Protypeと組み合わせて命令の複製もできる |
Chain of Responsibility |
Chainクラス (1) コンストラクタ (2) 自分の次を指すChainクラスのメンバ (3) 別のChainクラスを受け取って、(2)に登録するメソッド (4)自身の担当する処理をするメソッド。処理が無理だったら、(2)のこの処理(4)を呼ぶ。返値はBOOL型 |
複雑な制御フロー自体を分割して、分割したフロー自体をクラスにしてしまうイメージ。そして、chainクラスをつなげて、自分にできない処理は即、chainクラスに投げるという発想。chainクラスは自分の次のchainクラスをメンバに持つという仕組みで繋がっている。使われている所を見たことがないけど、制御フローをクラスに分割することでユニットテストがし易くなるとメリットがあります。ユニットテストを重視する開発手法が流行りの昨今では意外と有効かもしれません。 |
11.状態をそのもをクラスとして表現するパターン
パターン | 実装するもの | 概要 |
---|---|---|
State |
stateクラス (1)Privateコンストラクタ (2)自身のインスタンス取得メソッド (3)「状態変化に関する情報(時刻など)」とContextクラスを受取り、その条件に応じて別のstateクラスを、引数のContextクラスに(5)のメソッドを利用しセットするメソッド Contextクラス (4)stateクラスメンバ (5)stateクラスを引数に受け取り(4)にセットするクラス (6)自身と「条件変化に関する情報」を引数として(3)に渡し、(4)のstateクラスを現在の状況に合わせて切替えるメソッド |
状態をクラスにする発想が面白い。 ゲームプログラムで応用されるらしいので興味ある人は調べてみては? ちなみに業務システムなんかだと、全く使いません。ちなみにstateクラスはシングルトンで実装することがコツです。 |
12.データ構造をクラスで表現したパターン
パターン | 実装するもの | 概要 |
---|---|---|
Composite |
抽象クラス (1)容器と中身で共通するメソッドのインターフェース 中身クラス (2) 中身としての(1)の実装 容器クラス (3) 容器としての(1)の実装 |
容器と中身で同じ扱いができる時は、抽象クラスでまとめてポリモーフィズムを活用しようという話。(実際はそこまで単純でない)。興味深いパターンだが、実際に適用する場面にはなかなか出くわさない |
最後に
自分なりに分類完了。異論があればぜひコメントで教えていただきたいですね