Programs / 最終更新時間:2003年11月28日 03時09分55秒

概要

この資料はデザインパターン関係の様々なWebや本の文章をもとに自分の解釈を加えまとめたものです。作った動機は、各デザインパターンのよいところ、悪いところを一覧で見比べたいという個人的なものでしたが、もしかすると誰かのお役にたてるかもしれないということで公開しました。[1]

デザインパターンは少々ややこしいですが、大規模なソフトウェア開発をスマートに行うにはもってこいのものです。はじめてこの言葉を耳にした方は、作ったものを将来に活かすためにも、デザインパターンの導入を検討してみてはいかがでしょうか。[2]

  • [1]間違いのある可能性あり。また、基本的にC++のことしか考えてません
  • [2]やみくもに導入すると初めて目にする人には難解なものになりかねないので、ある程度の教育課程や段階的な導入を試みてみましょう。



オブジェクト指向における再利用のためのデザインパターン

目次

再設計の原因を有しているか?

自分の問題がそれらを有している場合は(「変更に対する設計」を読んで吟味)、再設計の回避に役立つパターンを導入する。

何が変動要素か?

「独立して変えられる設計要素」にあるものは、再設計することなしに、その要素だけを変更することができる。

どの要素を変更可能にしておきたいか?

パターンを導入する手順

  1. 「適用可能性」と「結論」をメインに、読む。
  2. 「構造」、「構成要素」、「協調関係」の節に戻る。
  3. 「サンプルコード」を見る。
  4. クラス(継承関係、インスタンス変数)を定義する。
  5. オペレーション名を定義する。
  6. 「実装」の節をヒントに、オペレーションを実装する。

変更に対する設計

クラス名を直接書いてオブジェクトを生成

それは特定のインターフェイスではなく、特定の実装を委ねることになる。これは将来の変更を複雑にしてしまうので、間接的にオブジェクトを生成するとよい。

Abstract Factory, Factory Method, Prototype

特定オペレーションへの依存

要求を満たすひとつの方法に、オペレーションの実装を委ねるのはよくない。要求を実装に依存しないようにすることで、コンパイル時と実行時の両方で実装を変更しやすくなる。

Chain of Responsibility, Command

ハードやプラットフォームへの依存

ハードやAPIに依存していると、移植や最新版に合わせることが難しくなる。したがって、プラットフォームへの依存をできるだけ小さくする。

Abstract Factory, Bridge

オブジェクトの表現や実装への依存

クライアントがオブジェクトの内部仕様を知っていると、オブジェクトの変更でクライアントも変更しなくてはならなくなる。クライアントに対してこうした情報を隠すことで、連鎖的な変更を抑制できる。

Abstract Factory, Bridge, Memeno, Proxy

アルゴリズムへの依存

アルゴリズムに依存したオブジェクトは、アルゴリズムを変更するときに変更しなくてはならない。それをしなくてすむようにするため、変更する可能性のあるアルゴリズムは局所化しなければならない。

Builder, Iterator, Strategy, Template Method, Visitor

密な結合

お互いに依存しあった、密な結合のクラスは、個別に再利用しにくい。結合度を低くするため、抽象化あるいは階層化技法を用いる。

Abstract Factory, Bridge, Chain of Responsibility, Command, Facade, Mediator, Observer

サブクラス化による機能の拡張

サブクラス化した新しいクラスは、初期化、後処理などのオーバーヘッドを持つ。正しく定義するためには、親クラスの深い理解が必要になる。また、クラス数の爆発を引き起こすこともある。

継承の変わりにオブジェクトコンポジションや委譲を使うという代替案がある。しかし、オブジェクトコンポジションを過度に利用すると理解しにくくなるため、1つのサブクラスを定義してそのインスタンスを既存のクラスのインスタンスと合成することで、カスタマイズした機能の設計を行う。

Bridge, Chain of Responsibility, Composite, Decorator, Observer, Strategy

簡単なクラス変更が不可能

簡単には修正できないクラスを修正しなければならないとき、あるいは変更によって多くの既存のクラスを修正しなければならないようなクラスがあるとき。そのような状況でクラスを修正する方法がある。

Adapter, Decorator, Visitor



生成に関するパターン

Abstruct Factory

仮想工場。作成するクラス群を、直接クラス名を書かずに生成する。
部品オブジェクトを生成するためのインタフェースを提供し、 クライアントから具象部品クラスを独立させる。抽象化された部品群の一元管理。


1.具象クラスを局所化する
2.部品の集合を容易に変更できるようになる
3.部品間の無矛盾性を促進する

×
4.新たな種類の部品に対応することが困難である。

独立して変えられる設計要素 : 部品オブジェクトの集合
ソースコードJava / C++

Builder

同じ作成過程で、異なる表現形式のオブジェクトを作る。
集約オブジェクトを段階的に作成するためのインタフェースを提供する。


1.Product オブジェクトの内部表現の変更が可能になる
2.生成や表現のためのコードを局所化する
3.Product オブジェクトの作成過程をより細かくコントロールできるようになる

独立して変えられる設計要素 : 複合オブジェクトの生成方法
ソースコードJava / C++

Factory Method

サブクラスに生成をまかせる。
オブジェクトを生成するインターフェースだけを規定して、実際にどの クラスをインスタンス化するかはサブクラスがオーバーライドして決める。


1.サブクラスに手がかりを提供する
2.パラレルなクラス階層をつなぐ

独立して変えられる設計要素 : インスタンス化されるサブクラス
ソースコードJava / C++

Prototype

クローン。
プロトタイプ(原型)オブジェクトをクローンすることで新たなオブジェクトの生成を行なう。


1.生成するオブジェクトの追加/削除を実行時に行える
2.値を変えることでオブジェクトの仕様を変更できる
3.構造を変えることでオブジェクトの仕様を変更できる
4.サブクラス化を減らす

×
5.prototype の各サブクラスが Clone オペレーションを実装しなければならず、これが困難な場合がある

独立して変えられる設計要素 : インスタンス化されるクラス
ソースコードJava / C++

Singleton

インスタンスが1つだけであることを保証。
あるクラスに対してインスタンスが1つしか存在しないことを保証し、それに アクセスするためのグローバルな方法を提供する。


1.インスタンスへのアクセスを厳しく制御することができる
2.名前空間を減らす
3.オペレーションや内部表現を詳細化できる
4.インスタンスの数を変えることができる
5.C++のクラスオペレーション(静的メンバ関数)よりも柔軟である

独立して変えられる設計要素 : クラスの唯一のインスタンス
ソースコードJava / C++

構造に関するパターン

Adapter

アダプター。再利用のためのインターフェース変換。
あるクラスのインタフェースを、クライアントが求める他のインタフェースへ変換する。


1.クラスに適用する Adapter パターンとオブジェクトに適用する Adapter パターンには、それぞれ異なるトレードオフがある

独立して変えられる設計要素 : オブジェクトへのインターフェイス
ソースコードJava / C++

Bridge

インターフェースと実装との分離。
抽象概念をその実装から分離する。


1.インタフェースと実装を分離する
2.拡張性を改善する
3.クライアントから実装の詳細を隠ぺいする

独立して変えられる設計要素 : オブジェクトの実装
ソースコードJava / C++

Composite

ツリー。部分−全体階層を表現。
分-全体階層を表現するために、オブジェクトを木構造に組み立てる。


1.composite を使って leaf を組み合わせて複雑なオブジェクトを構成することができ、クライアントのコードがleafを想定しているところでも、composite を扱うことができるようになる
2.クライアントをわかりやすくする
3.新しい種類の component を追加するのが容易になる

×
4.設計を過度に一般化してしまう

独立して変えられる設計要素 : オブジェクトの構造とコンポジション
ソースコードJava / C++

Decorator

デコレータ(装飾者)。オブジェクトに動的に機能(責任)を追加。


1.静的な継承よりも柔軟である
2.機能を満載したクラスを階層構造の上層で定義することを避けられる

×
3.decorator とそれが装飾している component は同一ではない
4.多くの小さなオブジェクト

独立して変えられる設計要素 : オブジェクトの責任(サブクラス化をともなわない)
ソースコードJava / C++

Facade

高レベルインターフェイス。(しかも低レベルを隠さない)
サブシステムに対する(Clientのための簡略化された)インターフェース。

サブシステム内に存在する複数のインタフェースに1つの統一インタフェー スを与える。


1.クライアントとサブシステムの間の結合を減らす


2.サブシステム内のクラスの公開、非公開

独立して変えられる設計要素 : サブシステムへのインターフェイス
ソースコードJava / C++

Flyweight

共有によるメモリの節約。
オブジェクトを共有するためには、 そのオブジェクトが使われているContextに依存しない情報のみを保持するようにしなければならない。

多数の細かいオブジェクトを効率よくサポートするために共有を利用する。


1.メモリ空間を節約できる

×
2.extrinsic(本質的でない)状態の受け渡しや探索、計算に関連する実行時のコストは増大するかもしれない
3.共有されるオブジェクトは、extrinsic(本質的でない)状態を持てないため、このことは、階層構造上のオブジェクト同士の通信方法などに大きな影響を及ぼすかもしれない。

独立して変えられる設計要素 : オブジェクトの格納コスト
ソースコードJava / C++

Proxy

代理/入れ物。
コストの高いオブジェクトの生成を最小限にしたり、ネットワーク上のオブジェクトに対する参照もProxyを介して行わせたりするときに使用。

あるオブジェクトへのアクセスを制御するために、そのオブジェクトの代理または入れ物を提供する。


1.remote proxy では、あるオブジェクトが異なるアドレス空間に属しているという事実を隠すことができる
2.virtual proxy では、要求があり次第オブジェクトを生成する、といった最適化を行うことができる
3.protection proxy と smart reference では、あるオブジェクトがアクセスされるときに、管理上の処理を追加して行うことができる
4.copy-on-writeといったクライアントには見せずに行える最適化が可能

独立して変えられる設計要素 : オブジェクトへのアクセス方法(確保されている場所による)
ソースコードJava / C++

振る舞いに関するパターン

Chain of Responsibility

チェーン。
要求を鎖状に連なったオブジェクト群に渡し、 受け取ったオブジェクトはその要求が自身で処理できる物であれば処理し、 自身では処理できないものであれば、successorを通じて要求を次のオブジェクトへと伝播する。 そうすることで、要求を送信する側と受信する側を分離。

1つ以上のオブジェクトに要求を処理する機会を与えることにより、要求を送 信するオブジェクトと受信するオブジェ クトの結合を避ける。


1.結合度を低くする
2.オブジェクトへ責任を割り振る際に柔軟性を高める

×
3.受信されるかどうかは保証されない

独立して変えられる設計要素 : 要求を満たすことができるオブジェクト
ソースコードJava / C++

Command

コマンド。要求のカプセル化。
要求をオブジェクトとしてカプセル化することによって、 要求そのものをパラメータとして他のオブジェクトに渡すようなことを可能と
する。


1.オペレーションを呼び出すオブジェクトと実行するオブジェクトを分離する
2.command は補助的なオブジェクトではない
3.複数の command を合成することができる
4.既存のクラスに変更を加える必要がないので、新しい command を容易に追加できる

独立して変えられる設計要素 : 要求を満たすタイミングと方法
ソースコードJava / C++

Interpreter

インタプリタ。
言語に対して、文法表現とそれを使用して文を解釈するインタプリタを一緒に定義する。


1.文法を変更したり拡張するのが容易である
2.文法を実装するのも容易である
4.表現を新しい方法で評価することを容易にする

×
3.複雑な文法は保守していくのが難しい

独立して変えられる設計要素 : 言語の文法と解釈
ソースコードJava / C++

Iterator

イテレータ。オブジェクトの入れ物に対する捜査方法を隠蔽。
約オブジェクトが基にある内部表現を公開せずに、その要素に順にアクセスする方法を提供する。


1.aggregate(集合)に対してさまざまな走査をサポートする
2.Iterator クラスは、Aggregate クラスのインタフェースを簡単なものにする
3.1つの aggregate に対して、複数の走査を実行することができる

独立して変えられる設計要素 : 集約オブジェクトの要素へのアクセス方法、捜査方法
ソースコードJava / C++

Mediator

オブジェクト間の相互作用をカプセル化。
オブジェクト同士がお互いを明示的に参照し合うことがないようにして、結合度を低める。

相互作用をカプセル化するオブジェクトを定義。


1.サブクラス化を制限する
2.Colleague(同僚)オブジェクトの結合度を低くする
3.オブジェクトのプロトコルを簡単なものにする(多対多の相互作用を一対多の相互作用に置き換える)
4.オブジェクトをどのように協力させるのかを抽出する

×
5.制御を集中化する(相互作用の複雑度を mediator 内の複雑度に置き換えるため、個々の Colleague オブジェクトよりも複雑になり得る)

独立して変えられる設計要素 : オブジェクト間での相互作用の様子
ソースコードJava / C++

Memento

オブジェクトの内部情報を保存。オブジェクトの状態をカプセル化を壊さずに別オブジェクトに託すことを可能にする。


1.カプセル化の境界を保つ
2.Originator(創作者)クラスを簡単なものにする

×
3.Memento オブジェクトを使うのはコストが高くつくかもしれない
5.Memento オブジェクトを管理する際の隠れたコスト(本来小規模な Caretaker オブジェクトが Memento オブジェクトを保存する際に大きなコストがかかることになるかもしれない)

独立して変えられる設計要素 : オブジェクトの外部に保存される私的な情報、および保存のタイミング
ソースコードJava / C++

Observer

通知。
データモデルとViewモデル間の依存関係を極小化する手法。

オブジェクト間に一対多の依存関係を定義する。


1.Subject クラスとObserver クラスの間の抽象的結合
2.ブロードキャスト通信のサポート
4.subject と observer を独立に変更することができるようになる
5.observer を、subject やその他の observer を修正せずに新たに追加することも可能になる

×
3.不慮の更新(observer 同士は互いに相手の存在を知らないため、subject の変化に伴うコストの総計をobserver が予測することはできない)

独立して変えられる設計要素 : あるオブジェクトに依存するオブジェクトの数(依存するオブジェクト群の変更方法)
ソースコードJava / C++

State

ステート。状態オブジェクトのカプセル化。
オブジェクトの内部状態が変化したときに、オブジェクトが振る舞いを変えるようにする。


1.状態に依存した振る舞いを局所化し、状態ごとに振る舞いを分割する
2.状態遷移を明確にする
3.もし ConcreteState オブジェクトがインスタンス変数を持たないなら、Context オブジェクト間で ConcreteState オブジェクトを共有することができる

×
4.個々の状態に対する振る舞いをStateのサブクラスに分配するため、クラスの数は増え、1つのクラスを利用する場合よりもコンパクトではなくなる

独立して変えられる設計要素 : オブジェクトの状態
ソースコードJava / C++

Strategy

戦略。「操作対象」と「操作方法」とを 委譲を用いて分離する手法。
行動の指針を与えるもの。

アルゴリズムの集合を定義し、各アルゴリズムをカプセル化してそれらを交換可能にする。


1.関連するアルゴリズムの集合
2.サブクラス化の代替案(これは、アルゴリズムを切り替え、理解し、拡張することを容易にする)
3.Strategy パターンは条件文を排除する
4.実装の選択(同じ振る舞いに対して異なる実装を提供することができる)

×
5.クライアントは、複数の ConcreteStrategy クラスの相違点を把握しておかなければならない
6.Strategy クラスと Context クラス間の通信に関するオーバーヘッド
7.増加するオブジェクトの数

独立して変えられる設計要素 : アルゴリズム
ソースコードJava / C++

Template Method

親クラスがサブクラスの関数を呼ぶ。処理手順のスケルトン化。
1つのメソッドにアルゴリズムのスケルトンを定義しておき、その中のいくつかのステップについては、サブクラスでの定義に任せる。

コードの再利用のための基本的な方法。


1.ライブラリのクラスにおいて共通の振る舞いを抜き出す手段になる

独立して変えられる設計要素 : アルゴリズムのステップ
ソースコードJava / C++

Visitor

ビジター。クラスオペレーションの分離。ある種の構造物または集合(口座、在庫、etc) の構成要素に対して行うべき「操作」を、それ自体から分離する手法。
あるオブジェクトの構造上の要素で実行されるメソッドを表現する。


1.新しいオペレーションを簡単に追加できるようにする
2.関連するオペレーションをまとめ、関連しないオペレーションは分離する
4.操作対象が1つのクラスである必要はない
5.状態を蓄積する

×
7.多くのクラスに機能を分配する場合には、新しいオペレーションを定義するために各クラスを変更しなければならなくなる
3.新しい ConcreteElement クラスを加えることは難しい
6.カプセル化に対して妥協を与えることになるかもしれない

独立して変えられる設計要素 : (複数の)オブジェクトに適用されるオペレーション(対応するクラスの変更を伴わない)
ソースコードJava / C++

関連書籍

デザインパターンをひととおり読んだあとには、こちらの書籍もおすすめです。

リファクタリング―プログラムの体質改善テクニック