GoFのデザインパターン(Design Pattern)の一つ、デコレータ(Decorator)をRubyのサンプルコードで紹介します。
デコレータは、既存のオブジェクトに対して簡単に機能の追加をするためのパターンです。 デコレータパターンを使うと、レイヤ状に機能を積み重ねて、必要な機能を持つオブジェクトを作ることができます。
デコレータの構成要素
デコレータは次の2つの要素で構成されます。
具体コンポーネント(ConcreteComponent):ベースとなる処理をもつオブジェクト デコレータ(Decorator):追加する機能を持つ
デコレータのメリット
* 既存のオブジェクトの中身を変更することなく、機能を追加できる * 組み合わせで様々な機能を実現できる * 継承よりも変更の影響を限定しやすい
サンプルソース
デコレータの概要を次のモデルを使って説明します。
SimpleWriter(具体コンポーネント): ファイルへの単純な出力を行う NumberingWriter(デコレータ): 行番号出力を装飾する機能を持つ TimestampingWriter(デコレータ): タイムスタンプを追加する機能を持つ
まず「ファイルへの出力機能」をもつSimpleWriterクラスを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
このクラスを動かしてみます。
1 2 3 | |
このコードを実行すると、sample1.txtに飾り気のない一行が入っていました。
タイムスタンプ/行番号クラスを作成する前に、それらのクラスの共通する機能を切り出したWriteDecoratorクラスを定義します。これは、Decoratorを複数作る場合に重複したコードをできるだけ書かないための工夫です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
タイムスタンプを出力するNumberingWriterクラスを定義します。
write_lineメソッドは、"#{@line_number} : #{line}"でlineに行番号を付加しています。そして、コンストラクタで受け取ったオブジェクト(SimpleWriter)のwrite_lineメソッドに処理を委譲しています。
このクラスは、デコレータパターンのDecoratorの役割を持ちます。
1 2 3 4 5 6 7 8 9 10 11 12 | |
最後にタイムスタンプを出力するNumberingWriterクラスを定義します。
write_lineメソッドは、"#{Time.new} : #{line}"でlineにタイムスタンプを付加して、オブジェクト(SimpleWriter)のwrite_lineメソッドに処理を委譲しています。
このクラスもデコレータパターンのDecoratorの役割を持ちます。
1 2 3 4 5 6 | |
ここまでがコーディング部分です。では、上のサンプルを動かしてみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
このようにデコレータパターンでは既存のクラス(SimpleWriter)を変更することなく、 機能を自由に組み合わせて使うことがてきています。
Rubyの標準ライブラリ Forwardableによる委譲
ここではクラスに対しメソッドの委譲機能を追加するForwardableを使って先ほどのソースをリファクタリングします。この委譲とは、ある機能を持つオブジェクトにその機能での処理を依頼することです。
先ほどのサンプルソースのWriterDecoratorクラスを以下の様に修正できます。
1 2 3 4 5 6 7 8 9 10 11 12 | |
Rubyの委譲:Forwardableとmethod_missingについて
Rubyでのメソッドの委譲は、forwardableとmethod_missingを使う方法があります。それぞれの特徴を生かしてうまく使い分けるとよさそうです。
* forwardableを使う場合、委譲しているメソッドを明確にすることができる * method_missingを使う場合は、メソッドが多い場合に有利、間違いがなくなる
Decoratorのモジュール化
Decoratorをモジュールにすることでも同様の機能を実現できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
このサンプルソースはGitHubにも置いています。
Adapter/Proxy/Decoratorの違い
Adapter/Proxy/Decoratorはいずれも「別のオブジェクトの代理」パターンと言えます。 この3つの違いをシンプルに説明すると次のようになります。
Adapter: オブジェクトの不適切なインターフェイスをラップする Proxy: ラップするオブジェクトと同じインターフェイスを持ち、一部の機能を受け持つ Decorator: 基本的なオブジェクトにレイヤ状に機能を追加する
Special Thanks
Amazon.co.jp: Rubyによるデザインパターン: Russ Olsen, ラス・オルセン, 小林 健一, 菅野 裕, 吉野 雅人, 山岸 夢人, 小島 努: 本
変更来歴
12/09 13:30 新規作成
12/10 09:00 GitHubへのサンプルソースの設置。導入文の修正
12/10 10:45 Adapter/Proxy/Decoratorの違いを追加
12/11 00:00 書籍へのリンクをAmazon アフィリエイトに変更
12/11 10:50 サンプルコードに説明書きを追加
06/21 17:00 Ruby2.0.0対応、読みづらい部分を修正