Ruby
デザインパターン

「Rubyによるデザインパターン」の要点と使いどころ

More than 3 years have passed since last update.

既出ではありますが、『Rubyによるデザインパターン』を読んだので自分なりにまとめました。

なお、「Rubyによるデザインパターン」についてより詳しく知りたい方は、Ruby 2.0.0で学ぶ、14個のデザインパターンを作りました[GoF][Design Pattern]を参照されると良いかと思います。サンプルソースなども充実しています。

Template Methodパターン

アルゴリズムを変更する。

要点

  • 抽象基底クラス(厳密にはRubyに抽象クラス/抽象メソッドは存在しないが、抽象クラス/抽象メソッドとして定義するという意味)に雛形となるメソッド(これをテンプレートメソッドという)を定義し、最低限(または既定)の動作のみを実装する。
  • 詳細な処理はサブクラスのメソッド(これをフックメソッドという)にてオーバーライドして実装する。

使いどころ

  • 処理全体の流れは同じだが、一部が異なる処理が複数ある場合に用いる。

具体例

  • WEBRickのGenericServerクラスはネットワークサーバとしてのすべての処理を実装している。このクラスを継承して変更したい部分だけをオーバーライドすることで、オリジナルとは一部が異なる処理を作成できる。
  • Rubyでクラスに定義するinitializeメソッドはフックメソッドである。

Strategyパターン

アルゴリズムを交換する。

要点

  • Template Methodパターンと同様の問題(一部が異なる処理が複数ある)を委譲によって解決する。
  • 処理が異なる部分を別のオブジェクトに委譲して分離する。委譲したクラスをストラテジといい、ストラテジを使う側のクラスをコンテキストという。
  • このパターンではコンテキストからストラテジにデータを渡す手段が必要になる。方法としては2つあり、1つめは必要なデータだけをストラテジのメソッド引数に渡す。2つめはコンテキスト自身のインスタントをストラテジのメソッド引数に渡す。ただし後者はコンテキストとストラテジが密結合になる。
  • ストラテジの処理が1つだけなら、ストラテジの代わりにProcオブジェクトにしてもよい。

使いどころ

  • 全体の流れは同じである複数の処理があり、一部の処理を変更する必要がある場合。
  • 委譲にすることでクラス間の結びつきを疎結合にしたい場合。

具体例

  • RDocの出力機能にはフォーマット(HTMLやXMLなど)が複数ある。これらをフォーマットごとに別のオブジェクトに分離している。

Observerパターン

変更に追従する。

要点

  • あるオブジェクト(サブジェクト)の値に変更があった時に、変更を検知したい全てのオブジェクト(オブザーバ)に対して、オブザーバ側のメソッドを通じて通知する。
  • サブジェクト側に必要なメソッドはモジュール化して、サブジェクトのクラスにincludeすると良い。Rubyの標準ライブラリにObservableモジュールがあるのでそれを使うことができる。

使いどころ

  • あるオブジェクトの状態が変わった時に、他のオブジェクトに変更を通知したい場合。

具体例

  • ActiveRecordはモデルの値が変更された時に、コールバックメソッド(before_saveやafter_updateなど)で変更を通知している。

Compositeパターン

部分から全体を組み立てる。

要点

  • 全てのオブジェクトは共通の基底クラス(コンポーネント)を継承する。
  • コンポーネントを継承し、子オブジェクトを持つオブジェクトをコンポジットという。これにより任意の深さのツリー構造ができる。
  • コンポーネントを継承し、子オブジェクトを持たない末端のオブジェクトをリーフという。子オブジェクトを持たないコンポジットを末端のオブジェクトにしても良い。

使いどころ

  • 大小様々な同じ振る舞いをもつオブジェクトに親子関係があり、ツリー構造を実装したい場合に用いる。

具体例

  • FXRuby(FOXというGUIライブラリのRuby版)のGUIのコンポーネント(テキストエリアやボタンなど)は、それぞれがコンポーネントであり、他のコンポーネントを子オブジェクトとして持つことができる。

Iteratorパターン

コレクションを操作する。

要点

  • 外部イテレータと内部イテレータがある。外部イテレータは集約オブジェクトとは別のイテレータオブジェクトにあるイテレータのことで、内部イテレータはコードブロックやProcオブジェクトを用いて利用する集約オブジェクトの中にあるイテレータのこと。
  • 内部イテレータを簡単に実装したい場合は、Enumerableモジュールをmix-inすることでイテレータメソッドを利用できるようになる。このmix-inに必要な条件は、内部イテレータメソッドにeachと名付けることと、子オブジェクトの<=>比較演算子に適切な実装を持つこと。

使いどころ

  • 集約オブジェクトの内部表現を公開せずに、その要素に順次アクセスする場合に用いる。

具体例

  • 内部イテレータはRubyのArrayやHashに実装されているeachメソッドなど。
  • 外部イテレータはRubyのIOクラスのファイルストリームなど。(実際にはIOクラスには内部イテレータも実装されている)

Commandパターン

命令を実行する。

要点

  • 特定の処理(コマンド)を別のオブジェクトに分離する。これはコードブロックでも代用ができる。
  • 処理は複数まとめて実行したり、元の状態に戻すこともできる。

使いどころ

  • 処理を特定のオブジェクトから分離する場合に用いる。

具体例

  • ActiveRecordのマイグレーションはモデルとは分離された処理になっており、ロールバックも実装することができる。

Adapterパターン

ギャップを埋める。

要点

  • アダブターはあるクラスを継承し、そのクラスの変数やメソッドをラッピングして別のインターフェイスを提供する。
  • また代替手段として、あるクラスもしくはインスタンスにメソッド(インスタンスの場合は特異メソッド)を追加することでも実現できる。

使いどころ

  • オブジェクトから別のオブジェクトに対するインターフェイスを提供する、あるいは他のオブジェクトとのインターフェイスの差異を吸収するためのラッパーオブジェクトが必要な場合。

具体例

  • ActiveRecordがデータベースへアクセスする際に、ユーザはデータベースの種類による差異を意識することなく同じインターフェイスで使えるようになっている。

Proxyパターン

オブジェクトに代理を立てる。

要点

  • プロキシーは本物のオブジェクトの参照を持ち、本物のオブジェクトと同じメソッドを定義して本物のように装う。
  • 本物のオブジェクトでは本来の処理のみを実装し、プロキシーでセキュリティ機能を実装したものを防御プロキシーという。
  • クライアント・サーバシステムで、クライアントにプロキシーを実装し、プロキシーがネットワーク越しにサーバへ要求を投げ、受け取った回答を返すものをリモートプロキシーという。
  • 本物のオブジェクトと同じ振る舞いをし、オブジェクトの実体が生成させるのをできるかぎり遅らせるためのプロキシーを仮想プロキシーという。
  • method_missingメソッドをプロキシーにオーバーライドすることで、予期せぬメソッド呼び出しに対応することができる。
  • プロキシーのメソッドの呼び出しは、sendメソッドを用いることでプロキシー側の実装を簡単にすることができる。

使いどころ

  • 本来の処理をプロキシーでラッピングすることで関心事の分離を行いたい場合。

具体例

  • RPCやSOAPクライアントなどリモートに要求を投げて応答を返す場合に仮想プロキシーのパターンが利用される。

Decoratorパターン

オブジェクトを改良する。

要点

  • 元のオブジェクトのプロキシーオブジェクトを基底クラスとし、追加機能を実装するオブジェクトに基底クラスを継承することで、元のオブジェクトを変更することなく機能を追加することができる。委譲はForwardableモジュールを使うことで簡単に実装できる。
  • Ruby特有の方法として、他に2津の方法がある。1つめは、ailasキーワードと特異メソッドを使うことで動的に機能を変更することができる。2つめは、moduleをextendして動的に機能を変更することができる。

使いどころ

  • 元のオブジェクトを変更すること無く、オブジェクトに機能を追加したい場合。
  • 元のオブジェクトを簡潔に保ちつつ、委譲により拡張機能を追加したい場合。

具体例

  • ActiveSupportのalias_method_chainはエイリアスで機能を拡張している。

Singletonパターン

唯一を保証する。

要点

  • シングルトンはただ1つのインスタンスしか存在しないオブジェクトのこと。
  • Rubyでの実装は、クラスのインスタンス変数に唯一のインスタンスをセットし、メソッドでインスタンスを取り出す。また、newメソッドをprivateにして新たなインスタンスが作られないようにする。これらの実装は、Singletonモジュールをincludeすることでも実装できる。
  • この他に、クラスをシングルトンとして定義する(クラスメソッド、クラス変数をクラスに定義する)方法や、モジュールをシングルトンにする方法もある。

使いどころ

  • アプリケーションの中で唯一のオブジェクトであり、唯一である必要がある場合に用いる。

具体例

  • ActiveSupportは複数形や単数形に変換する機能がある。この変換定義はアプリケーション内で必ず同じでなければならない。そのためInflectionsクラスはシングルトンで作られている。
  • rakeのビルドタスクに関する情報を読み込むオブジェクト(Rake::Application)もシングルトンである。

Factoryパターン

正しいクラスを選び出す。

要点

  • 使用するクラスの選択をサブクラスに押し付けることで多様な使い方を提供する。これをFactory Methodパターンと言い、クラスを選択するメソッドをファクトリメソッドという。
  • クラスの種類が増えるとファクトリメソッドも増えるため、ファクトリメソッドにパラメータを持たせることで汎用化する。
  • クラスの組み合わせに矛盾がないようにあらかじめ組み合わされたファクトリをアブストラクトファクトリという。またこの方法をAbstract Factoryパターンという。

使いどころ

  • 使用するクラス(部品)を時々に応じて選択したい場合。

具体例

  • ActiveRecordがDBConnectionを得るためにデータベース名を指定して使用する部品(この場合はメソッド)の選択を行っている。

Buildパターン

オブジェクトを組み立てやすくする。

要点

  • 複雑なコンポーネントの組み立てを簡単に使えるようにするとともに、内部ロジックを隠蔽する。また、コンポーネントの組み立て方に誤りはないか、不足はないかなどのチェックを設けることもできる。
  • より簡単に使えるようにするためにmethod_missingメソッドを使ったマジックメソッドを使うテクニックがある。

使いどころ

  • コンポーネント(部品)がたくさんあり、それらを組み立てる必要がある場合。

具体例

  • メールの宛先や本文を組み立てるMailFactoryはビルダーになっている。
  • マジックメソッドを使用している例としては、ActiveRecordのfind_by系メソッドがある。

Interpreterパターン

専用の言語で組み立てる。

要点

  • プログラム言語では実装しにくい(非常に大変な)処理を、簡単に行えるようにするためのAPIを提供する。
  • さらにパーサを作ることで、プログラム言語の文法とは全く関係ない別の言語を作ることができる。

使いどころ

  • 使用範囲が明確であり、何度もあるいは多くの人がそれを使う必要があり、より簡単に使うための機能を提供したい場合。ただし、RubyはDSLを作ることができるので、多くの場合はDSLを使ったほうが問題を容易に解決できる。

具体例

  • Rubyそのものは、C言語で作られたインタープリタ型言語である。
  • SQLや正規表現などもインタープリターの一種。