GoFのデザインパターンのひとつ、デコレータ(Decorator)をRubyのサンプルコードで紹介します。
デコレータは、既存のオブジェクトに対して簡単に機能の追加をするためのパターンです。
デコレータパターンを使うと、レイヤ状に機能を積み重ねて、必要な機能をもつオブジェクトを作ることができます。
🎂 デコレータの構成要素
デコレータは次の2つの要素で構成されます。
- 具体コンポーネント(ConcreteComponent):ベースとなる処理をもつオブジェクト
- デコレータ(Decorator):追加する機能をもつ
🐞 デコレータのメリット
- 既存のオブジェクトの中身を変更することなく、機能を追加できる
- 組み合わせでさまざまな機能を実現できる
- 継承よりも変更の影響を限定しやすい
🐹 サンプルソース
デコレータの概要を次のモデルを使って説明します。
SimpleWriter
(具体コンポーネント):ファイルへの単純な出力を行うNumberingWriter
(デコレータ):行番号出力を装飾する機能をもつTimestampingWriter
(デコレータ):タイムスタンプを追加する機能をもつ
まず「ファイルへの出力機能」をもつSimpleWriter
クラスを作成します。
# ファイルへの単純な出力を行う (ConcreteComponent) |
このクラスを動かしてみます。
writer = SimpleWriter.new('sample1.txt') |
このコードを実行すると、sample1.txt
に飾り気のない一行
が入っていました。
タイムスタンプ/行番号クラスを作成する前に、それらのクラスの共通する機能を切り出したWriteDecorator
クラスを定義します。これは、Decoratorを複数作る場合に重複したコードをできるだけ書かないための工夫です。
# 複数のデコレータの共通部分(Decorator) |
タイムスタンプを出力するNumberingWriterクラスを定義します。
write_lineメソッドは、"#{@line_number} : #{LINE}"
でLINEに行番号を付加しています。そして、コンストラクタで受け取ったオブジェクト(SimpleWriter
)のwrite_line
メソッドに処理を委譲しています。
このクラスは、デコレータパターンのDecoratorの役割を持ちます。
# 行番号出力機能を装飾する(Decorator) |
最後にタイムスタンプを出力するNumberingWriter
クラスを定義します。
write_lineメソッドは、"#{Time.new} : #{LINE}"
でLINEにタイムスタンプを付加して、オブジェクト(SimpleWriter
)のwrite_line
メソッドに処理を委譲しています。
このクラスもデコレータパターンのDecoratorの役割を持ちます。
# タイムスタンプ出力機能を装飾する(Decorator) |
ここまでがコーディング部分です。では、上のサンプルを動かしてみます。
f = NumberingWriter.new(SimpleWriter.new("file1.txt")) |
このようにデコレータパターンでは既存のクラス(SimpleWriter)を変更することなく、
機能を自由に組み合わせて使うことがてきています。
😀 Rubyの標準ライブラリForwardableによる委譲
ここではクラスに対しメソッドの委譲機能を追加するForwardable
を使って先ほどのソースをリファクタリングします。この委譲とは、ある機能をもつオブジェクトにその機能での処理を依頼することです。
先ほどのサンプルソースのWriterDecorator
クラスを次のように修正できます。
require "forwardable" |
🎃 Rubyの委譲:Forwardableとmethod_missingについて
Rubyでのメソッドの委譲は、forwardable
とmethod_missing
を使う方法があります。それぞれの特徴を活かしてうまく使い分けるとよさそうです。
forwardable
を使う場合、委譲しているメソッドを明確にできるmethod_missing
を使う場合は、メソッドが多い場合に有利、間違いがなくなる
🎉 Decoratorのモジュール化
Decoratorをモジュールにすることでも同様の機能を実現できます。
module TimestampingWriter |
🤔 Adapter/プロキシ/Decoratorの違い
Adapter/プロキシ/Decoratorはいずれも「別のオブジェクトの代理」パターンといえます。
この3つの違いをシンプルに説明すると次のようになります。
- Adapter: オブジェクトの不適切なインターフェイスをラップする
- Proxy: ラップするオブジェクトと同じインターフェイスを持ち、一部の機能を受けもつ
- Decorator: 基本的なオブジェクトにレイヤ状に機能を追加する