Template Method パターン
id:hyuki さんのデザインパターン本から Adapter パターンを移植してみます。
#!/usr/local/bin/ruby class AbstractDisplay def display self.open 5.times do self.print end self.close end end class CharDisplay < AbstractDisplay def initialize(ch) @ch = ch end def open Kernel.print "<<" end def print Kernel.print "#@ch" end def close puts ">>" end end class StringDisplay < AbstractDisplay def initialize(string) @string = string end def open print_line end def print puts "|#@string|" end def close print_line end def print_line Kernel.print "+" @string.length.times do Kernel.print "-" end puts "+" end private :print_line end displays = [ CharDisplay.new('H'), StringDisplay.new('Hello, world.'), StringDisplay.new('こんにちは') ] displays.each do |d| d.display end
実行結果は、
<<HHHHH>> +-------------+ |Hello, world.| |Hello, world.| |Hello, world.| |Hello, world.| |Hello, world.| +-------------+ +----------+ |こんにちは| |こんにちは| |こんにちは| |こんにちは| |こんにちは| +----------+
です。一瞬、はまちちゃんが出たかと思いました。
Template Method はいわばコールバックをサブクラスで実装するようなものです。なので yield との相性がよさそうです。このコードでは AbstractDisplay#display の中で
self.open 5.times do self.print end self.close
として、前処理(open) と後処理 (close) の間の処理が 5.times.do
とか決めうちな感じになっています。(だからこそ Template Method なのですが) が、出力されるテキストは別に 5 回に限定する必要もなく、クラスを利用する側から決められてもよさそう。
ということでここを yield にしてみます。
class AbstractDisplay def display self.open yield(self) self.close end end
すると、
displays.each do |d| d.display do |d| 3.times { d.print } end end
と中核の処理を、クラスを利用する側が記述できるようになります。ただ、これだとクラスを利用する側が中核の処理を必ず実装しなければならなくなってちょっと嫌なので、デフォルトでは 5.times do print; end
をするとして、ブロックを受け取ったときだけその処理を実行するようにしてみます。
あるメソッドがブロックを受け取ったかどうかを調べる方法はいくつかあるようです。
Kernel.block_given?
で調べる- アンパサンドつき引数 (Proc オブジェクト) を使う
アンパサンドつき引数は、クロージャを実現するためにも使われたりします。ここでは柔軟性の高い後者でやってみます。
class AbstractDisplay def display(&block) self.open if (block) block.call(self) else 5.times do self.print end end self.close end end
とすると、
displays.each do |d| d.display end
では先と同じ出力、
displays.each do |d| d.display do |d| 3.times { d.print } end end
だとブロックの処理が優先されて、出力が三回になります。