Decorator
Rubyによるデザインパ(ry
提供は、
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 12人 クリック: 193回
- この商品を含むブログ (58件) を見る
本日はDecoratorパターンを勉強します。
Decoratorパターンは、既存のオブジェクトに対しての機能追加を実現するパターンです。
decorateは、装飾する、という意味になります。
オブジェクトに機能という名の装飾を施していくパターンかな、と。
上の最後の1行、スベってる感があるな。今日は不調かも。
JavaのDecoratorパターンといえば、
BuffererdReaderといったIOのAPIが思い浮かびます。
BuffererdReaderの場合は、ファイル読み込みというベース機能に、バッファリング機能を装飾してると考えることができます。
今回は本に載っているとおり、
ファイル出力機能をDecoratorパターンで実装してみます。
ただし、ファイルを出力する際に、必要に応じて以下の機能を追加できるようにします。
- 行番号の表示
- タイムスタンプの表示
まずはファイル出力を行うSimpleWriteクラスを作成します。
名前のとおり、シンプルにファイル出力のみを行うクラスです。
class SimpleWriter def initialize(path) @file = File.open(path, "w") end def write_line(line) @file.print(line) @file.print("\n") end def pos @file.pos end def rewind @file.rewind end def close @file.close end end
データを出力するwrite_lineメソッド、ファイル出力ポジションをコントロールするposメソッド、rewindメソッド、ファイル出力を閉じるcloseメソッドを実装しています。
SimpleWriteクラスを呼び出すプログラムは以下のようになります。
f = SimpleWriter.new("file1.txt") f.write_line("Hello world.") f.close
結果として、file1.txtファイルにHello world.が出力されます。
上記のSimpleWriterクラスに、行番号出力機能を装飾するNumberingWriterクラスを作成します。
NumberingWriterクラスは以下のとおりです。
class NumberingWriter < WriterDecorator def initialize(real_writer) super(real_writer) @line_number = 1 end def write_line(line) @real_writer.write_line("#{@line_number} : #{line}") end end
NumberingWriterクラスは、
コンストラクタの引数で受け取ったオブジェクトをインスタンス変数に保持します。
write_lineメソッドでは、行番号出力を施したあと、
コンストラクタで受け取ったオブジェクトに処理を委譲します。
つづいて、タイムスタンプ出力機能を装飾するTimestampoingWriterクラスです。
class TimestampingWriter < WriterDecorator def write_line(line) @real_writer.write_line("#{Time.new} : #{line}") end end
また、NumberingWriterクラス、TimestampingWriterクラスはともにWriteDecoratorクラスを継承しています。
WriteDecoratorクラスは、NumberWriterクラス、TimestampingWriterクラスの共通機能を切り出したクラスになります。
話が前後しますが、このクラスでインスタンス変数real_writerを定義しているのが、Decoratorパターンのポイントになります。
class WriterDecorator def initialize(real_writer) @real_writer = real_writer end def write_line(line) @real_writer.write_line(line) end def pos @real_writer.pos end def rewind @real_writer.rewind end def close @real_writer.close end end
では、呼び出しもとプログラムを作成してみます。
f = NumberingWriter.new(SimpleWriter.new("file1.txt")) f.write_line("Hello world.") f.close
ファイルに出力された結果は以下のとおりです。行番号が付加されます。
1 : Hello world.
同様にタイムスタンプの出力は、
f = TimestampingWriter.new(SimpleWriter.new("file1.txt")) f.write_line("Hello world.") f.close
ファイルに出力された結果は以下のとおりです。タイムスタンプが付加されます。
Fri Jul 03 17:57:24 +0900 2009 : Hello world.
で、行番号とタイムスタンプの両方の出力を追加してみます。
f = TimestampingWriter.new(NumberingWriter.new(SimpleWriter.new("file1.txt"))) f.write_line("Hello world.") f.close
ファイルに出力された結果は以下のとおりです。行番号とタイムスタンプが付加されます。
1 : Fri Jul 03 17:58:56 +0900 2009 : Hello world.
以上がDecoratorパターンのサンプルになります。
Decoratorパターンのクラス図を見てみます。
- Component
- ConcreteComponent
- ベースとなる処理をもつクラス。SimpleWriterクラスが該当する。
- Decorator
- ベースとなる処理に機能を装飾するクラス。NumberingWriterクラス、TimestampingWriterクラスが該当する。
ここから、RubyらしくDecoratorパターンをイジってみます。
NumberingWriterクラス、TimestampingWriterクラスの基底クラスであるWriterDecoratorクラスはForwardableモジュールを使用するとシンプルになります。
require "forwardable" class WriterDecorator extend Forwardable def_delegators :@real_writer, :write_line, :pos, :rewind, :close def initialize(real_writer) @real_writer = real_writer end end
もともと、WriterDocoratorクラスは、real_writerオブジェクトへ処理を委譲していました。
処理の委譲は、forwardableモジュールのdef_delegatorsメソッドで行うことができます。
#ということを初めて知りました。勉強して良かった。
method_missingメソッドでも似たようなことがでますが、forwardableの方がわかりやすいです。
#method_missingは委譲するメソッドの数が多くなった場合に便利と本には書いています。このへんは上手く使い分けると良いですね。
まだまだあります。
次は実行時にオブジェクトを拡張する方法を見てみます。
f = SimpleWriter.new("file1.txt") class << f alias old_write_line write_line def write_line(line) old_write_line("#{Time.new} : #{line}") end end
面白いのはaliasメソッドで、既存のwrite_lineメソッドにエイリアス(old_write_line)を付けているところですね。
このあと、write_lineメソッドは再定義されて上書きされてしまうのですが、
old_write_lineメソッドは上書きされる前のメソッドを参照したままになります。
#これはスゴい。Rubyのスゴさがまた一つわかった気がする。。
あとは、モジュールを使って機能を追加する方法も紹介しています。
module NumberingWriter attr_reader :line_number def write_line(line) @line_number = 1 unless @line_number super("#{@line_number} : #{line}") @line_number += 1 end end module TimestampingWriter def write_line(line) super("#{Time.new} : #{line}") end end
NumberingWriter、TimestampingWriterをモジュールとして定義すると、
呼び出しもとプログラムは以下のようになります。
f = SimpleWriter.new("file1.txt") f.extend(NumberingWriter) f.extend(TimestampingWriter) f.write_line("Hello world3.") f.close
実行時にモジュールを追加する場合はextendメソッドを使います。
#このへんの感覚がRubyは難しいな。まだ慣れない。
委譲、じゃなかった、
以上、Decoratorパターンのお話でした。