楽水

人々の創造が自由に表現できる舞台づくり

デザインパターン

デザインパターンとは

投稿日:2020年10月1日 更新日:

デザインパターンとは、ソフトウェア設計者(アーキテクト)が、過去に編み出した設計ノウハウを蓄積し、名前をつけて、再利用しやすいようにカタログ化したものです。
デザインパターンといえばオブジェクト指向における再利用のためのデザインパターンという書籍が有名で、そこで定義されたパターンは、通称、GoFのデザインパターンと呼ばれています。

ここでは、GoFのデザインパターンについて以下の観点で解説します。

  • GoFのデザインパターンとは何か
  • なぜデザインパターンが重要なのか
  • ソフトウェアの再利用性を上げるための2つのメカニズム
  • GoFのデザインパターンの種類

GoFのデザインパターンとは何か

GoF(ゴフ)とは、オブジェクト指向における再利用のためのデザインパターンの著者であるエーリヒ・ガンマ、リチャード・ヘルム、ラルフ・ジョンソン、ジョン・ブリシディースの4人をGang of Four(4人組)とし、それを略した言い方です。
GoFのデザインパターンは、オブジェクト指向のソフトウェア設計者が、過去に編み出した設計ノウハウを蓄積し、名前をつけて、再利用しやすいようにカタログ化したもので、その目的は、ソフトウェア開発の生産性や保守性を上げることです。
GoFのデザインパターンは23個あり目的別にカテゴライズされています。

なぜデザインパターンが重要なのか

建築家(アーキテクト)、Christopher Alexanderはデザインパターンについて次のように言っています。

それぞれのパターンは身の回りで何回も起きる問題、および、それぞれの問題に対する解法のポイントを記述している。
そこで我々は、これらの解法を何万回でも利用することができる。
同じ問題に対する同じ解法を何度も何度も最初から考え直さずに済むというわけだ。

これは建築物や街の設計におけるパターンについて論じたものですが、オブジェクト指向によるソフトウェア設計にもあてはまります。
ソフトウェアの解法は、壁やドアの代わりにオブジェクトやインターフェースによって記述されますが、いずれのパターンもそれぞれの問題に対する解法になっている点では同じです。
デザインパターンを活用することで、同じような問題に対する解法をゼロから考える必要がなくなり、それだけ、ソフトウェア開発のスピードを上げることができます

ソフトウェアの再利用性を上げるための2つのメカニズム

ソフトウェアを設計するときに重要なのがいかにソフトウェアの再利性を上げるかということです。
GoFのデザインパターンは、継承合成という2つのメカニズムによって、ソフトウェアの再利用性の向上という問題に対する解法を提示しています。
それぞれ見ていきましょう。

継承

ソフトウェアの再利用性を上げるためのメカニズムとしてわかりやすいのがクラスの継承ではないでしょうか。
クラスをベースとしたオブジェクト指向によるソフトウェア開発では、既存のクラスの属性や操作を継承して新しいクラスを定義することができます。
つまり、既にあるクラスを再利用して新しいクラスを定義できるということです。
この場合、再利用されるクラスを親クラスとかスーパークラスといい、再利用するクラスを子クラスとはサブクラスといいます。
以下の図は、スーパークラス(SuperClass)の操作(operation)をサブクラス(SubClass)が継承している例です。

継承の特徴と欠点は以下です。

継承の特徴

  • クラス継承はオブジェクト指向型のプログラミング言語によって直接支援されているので再利用の方法が実装時(静的)に確定する
  • クラス継承の場合、親クラスの操作の内容を理解した上で、その内容を再利用するので、親クラスの内容が見えるという意味でホワイトボックス再利用と言われる

継承の欠点

  • 継承はコンパイル時に確定されるので、実行時に親クラスの実装方法を変更することができない(柔軟性が低い)
  • 子クラスの実装は親クラスの実装と大変密接に関係しているので、親クラスの実装を変更すると子クラスにも影響する(結合度が高い)

なお、親クラスの実装依存を下げる方法として抽象クラスを継承する方法があります。
抽象クラスは、継承するクラスが自由に定義できる操作(インターフェース)が定義されており、親クラスの実装部分が限定されているからです。

合成

ソフトウェアの再利用性を上げるためのもう一つのメカニズムが合成です。
これは、クラスが相手のクラスを利用するとき、相手のクラスを継承して、その内容を再利用するのではなく、相手のクラスを合成することで、その内容を再利用する方法です。
つまり、相手に「なる(be)」のでなく相手を「持つ(have)」ことで再利用するのです。
ここで重要なのが、合成するときは、相手のクラスの操作(インターフェース)を持つのであって、操作の動き(実装)を直接知っているわけではないということです。
操作に対する動きは、実行時に設定することができます。
つまり、合成する場合、必要に応じて、実行時に操作に対する動きを変えることができるのです。
オブジェクトが同じ操作に対して様々な動きをする性質のことをポリモーフィズム(多相性、多態性)といいます。
合成は、このポリモーフィズムを活かしたメカニズムです。
以下の図は、GoFのデザインパターンの中のStrategyパターンの構造ですが、ContextクラスがStrategyクラスを合成しています。

Strategyクラスは抽象クラスで、その操作、algorithmInterfaceはインターフェースのみ定義されており中身(実装)は定義されていません。
algorithmInterfaceインターフェースの中身(実装)は、Strategyクラスを継承した具体的なクラスであるConcreteStrategyA、ConcreteStrategyB、ConcreteStrategyCで定義されます。
この場合、ContextクラスのcontextInterfaceは、StrategyクラスのalgorithmInterfaceというインターフェスを利用しており、その動きは、実行時に、どのConcreteStrategyが選択されるかによって変わってきます。
合成の特徴と欠点は以下です。

合成の特徴

  • クラスを合成する場合、操作に対する動きは実行に設定することができるので、再利用の方法は実行時(動的)に確定する
  • クラスを合成する場合、相手のクラスの実装が隠蔽(カプセル化)されているのでブラックボックス再利用と言われる

合成の欠点

  • クラスを定義するとき他のクラスから再利用されることを考慮して、操作(インターフェース)を注意深く設計する必要がある(難易度が高い)
  • 相手に合成される役割を持つクラスが多く設計されオブジェクトの数が増える(複雑度が高い)

しかし、合成を使うと以下のようなメリットを得ることができます。

  • 合成の場合、継承と異なり、実行時に合成相手の実装方法を変更することができるので柔軟性は高くなる
  • 合成の場合、継承と異なり、相手のクラスの実装に依存しているわけではないので結合度は低くなる

以上、継承と合成について見てきましたが、再利用に対する合成の力を継承と同じくらい高める技法があります。
それは委譲です。

委譲(デリゲーション:delegation)

クラス継承の場合、親クラスが持つ性質(属性や操作)を、継承することで子クラスが再利用します。
委譲の場合、親クラスが持つ性質(属性や操作)を、継承するのではなく、合成することで再利用するのです。
要求を受け取ったオブジェクトは、合成相手に操作を委譲します。
さきほど例で示したStrategyパターンも委譲を利用したパターンです。
ContextはStrategyに操作の実装(アルゴリズム)を委譲しているのです。
GoFのデザインパターンでは、StateパターンやVisitorパターンも委譲を利用したパターンです。

GoFのデザインパターンの種類

最後にGoFのデザインパターンを
オブジェクトを生成するプロセス扱う「生成に関するパターン」
クラスやオブジェクトの構成を扱う「構造に関するパターン」
クラスやオブジェクトの通信方法を特徴付け責任を分担する「振る舞いに関するパターン」
に分けて紹介します。

生成に関するパターン

Abstract Factory
関連する一連のインスタンスを状況に応じて、適切に生成する方法を提供する。
Builder
複合化されたインスタンスの生成過程を隠蔽する
Factory Method
実際に生成されるインスタンスに依存しない、インスタンスの生成方法を提供する
Prototype
同様のインスタンスを生成するために、原型のインスタンスを複製する。
Singleton
あるクラスについて、インスタンスが単一であることを保証する。

構造に関するパターン

Adapter
元々関連性のない2つのクラスを接続するクラスを作る。
Bridge
クラスなどの実装と、呼出し側の間の橋渡しをするクラスを用意し、実装を隠蔽する。 Yes Yes
Composite
再帰的な構造を表現する。
Decorator
あるインスタンスに対し、動的に付加機能を追加する。
Filterとも呼ばれる。
Facade
複数のサブシステムの窓口となる共通のインタフェースを提供する。
Flyweight
多数のインスタンスを共有し、インスタンスの構築のための負荷を減らす。
Proxy
共通のインタフェースを持つインスタンスを内包し、利用者からのアクセスを代理する。
Wrapperとも呼ばれる。

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

Chain of Responsibility
イベントの送受信を行う複数のオブジェクトを鎖状につなぎ、それらの間をイベントが渡されてゆくようにする。
Command
複数の異なる操作について、それぞれに対応するオブジェクトを用意し、オブジェクトを切り替えることで、操作の切替えを実現する。
Interpreter
構文解析のために、文法規則を反映するクラス構造を作る。
Iterator
複数の要素を内包するオブジェクトのすべての要素に対して、順番にアクセスする方法を提供する。
反復子。
Mediator
オブジェクト間の相互作用を仲介するオブジェクトを定義し、オブジェクト間の結合度を低くする。
Memento
データ構造に対する一連の操作のそれぞれを記録しておき、以前の状態の復帰または操作の再現が行えるようにする。
Observer
インスタンスの変化を他のインスタンスから監視できるようにする。
ListenerやPublisher/Subscriber(Pub/Sub)とも呼ばれる。
State
オブジェクトの状態を変化させることで、処理内容を変えられるようにする。
Strategy
データ構造に対して適用する一連のアルゴリズムをカプセル化し、アルゴリズムの切替えを容易にする。
Template Method
あるアルゴリズムの途中経過で必要な処理を抽象メソッドに委ね、その実装を変えることで処理が変えられるようにする。
Visitor
データ構造を保持するクラスと、それに対して処理を行うクラスを分離する。

以上、今回は、GoFのデザインパターンについて解説しました。

デザインパターンを学ぶことは、先人たちの知恵を学ぶことで、オブジェクト指向における設計のセンスを身につけることです。
ただプログラムをつくるのではなく品質の高いプログラムをつくるための技術を身につけたい方は、ぜひ、「オブジェクト指向における再利用のためのデザインパターン」を読むことをお勧めします。
また、GoFのデザインパターンの実装例をJavaで提示して解説している「Java言語で学ぶデザインパターン入門」も有名ですので、Javaを通して具体的な実装方法を学びたい方にお勧めします。

-デザインパターン
-, ,

執筆者:


  1. […] さらによくわかります。 この間接化ですが、ソフトウェアを設計するときの定石を集めたGoFのデザインパターンの中では、メディエーター(Medicator)パターンとして紹介されています。 […]

  2. […] リ 集約の複雑な生成過程をカプセル化する役割がファクトリです。 ファクトリは、デザインパターンのFactoryMethodパターンやAbstractFactoryパターンに該当します。 なお、集約が、それが保 […]

関連記事

オブジェクトの性質【同一性、等価性、不変性、参照透過性】

ここでは、オブジェクトの性質について、以下の観点で解説します。 同一性 等価性 不変性 参照透過性 同一性と等価性 同一性と不変性 不変性と参照透過性 参照オブジェクトと値オブジェクト 参照型と値型 …

ソフトウェアの設計原則②コマンド・クエリ分離の原則(CQS)

ソフトウェアの設計原則①:SOLIDの原則という記事で、変化に強いソフトウェアの代表的な特徴として以下をあげ、それを実現する、ソフトウェアの設計原則の一つ、SOLIDについて解説しました。 保守性が高 …

ソフトウェアの設計原則①SOLIDの原則

昨今の特徴を端的に表すと 個の時代 組織から個人へ。 心の時代 「もの」から「こと」へ。 ということになるのではないでしょうか。 激しく変化する多様な嗜好に合わせて、 ビジネスの変化が加速化 ビジネス …

ドメイン駆動設計入門【DDDをわかりやすく解説】

突然ですが、エンジニアの皆さま、Javaで開発したWebアプリケーションの構成、このようになっていませんか? データとgetter/setterだけのオブジェクト(JavaBean) 画面のコントロー …

MVCとは【本来の仕組を詳しく解説】

今回は、 MVCって聞いたことがあるけど今ひとつわからないという方 アプリケーション開発でMVCをどう使うのか知りたい方 最近、よく使われるMVVMの基本となる本来のMVCについて根本的に理解したいと …