🍣
Rubyによるデザインパターン~Template Method~
概要
以下で書いた一般的な原則編
の続き。
ラスオルセン著「Rubyによるデザインパターン」の第三章Template Method
を読んで整理していく。
例題
月次のレポートを出力する処理の例
ごちゃごちゃしていて可読性が低く、フォーマットが増える度に既存の実装を修正する必要があり、フォーマットが壊れるリスクがある。
class Report
def initialize
@title = 'monthly report'
@text = ['going well', 'great']
end
def output_report(format)
if format == :plain
puts("*** #{@title} ***")
elseif format == :html
puts('<html>')
puts('<head>')
puts("<title>#{@title}</title>")
puts('</head>')
puts('<body>')
else
raise "Unknown format: #{format}"
end
@text.each do |line|
if format == :plain
puts(line)
else
puts("<p>#{line}</p>")
end
end
if format == :html
puts('</body>')
puts('</html>')
end
end
end
変わるものと変わらないものを分離する。
処理は以下で構成されている。
- 特定フォーマットに必要なヘッダ情報を出力する
- タイトルを出力する
- レポート本体の各行を出力する
- フォーマットに要求される残りの要素を出力する
抽象基底クラスを定義して、基本的なステップを実行する主となるメソッドをもたせ、ステップの詳細はサブクラスに持たせる
出力フォーマットごとにサブクラスを作成する
クラス図で表すと以下のような感じ。
Rubyでは抽象メソッド
や抽象クラス
を持たないため、抽象メソッドを呼び出そうとしたとき以下のように例外を投げる。
class Report
def initialize
@title = 'monthly report'
@text = ['going well', 'great']
end
def output_report
output_start
output_head
output_body_start
output_body
output_body_end
output_end
end
def output_body
@text.each do |line|
output_line(line)
end
end
def output_start
raise 'Called abstract method: output_start'
end
def output_head
raise 'Called abstract method: output_head'
end
def output_body_start
raise 'Called abstract method: output_body_start'
end
def output_line
raise 'Called abstract method: output_line'
end
def output_body_end
raise 'Called abstract method: output_body_end'
end
def output_end
raise 'Called abstract method: output_end'
end
end
Report クラスのサブクラスは以下の通り。
# フォーマットがHTMLの場合
class HTMLReport < Report
def output_report
puts('<html>')
end
def output_head
puts('<head>')
puts("<title>#{@title}</title>")
puts('</head>')
end
def output_body_start
puts('<body>')
end
def output_line(line)
puts("<p>#{line}</p>")
end
def output_body_end
puts('</body')
end
def output_end
puts('</html>')
end
end
# フォーマットがtextの場合
class PlainTextReport < Report
def output_start
end
def output_head
puts("***#{@title}***")
puts
end
def output_body_start
end
def output_line(line)
puts(line)
end
def output_body_end
end
def output_end
end
end
フックメソッド
PlainTextReport
を見るとテキストでは不要にも関わらずoutput_start
やoutput_end
がオーバーライドされている。
このような場合は、基底クラス側で標準となる実装を提供するほうが合理的。
Template Methodの具象クラスでオーバーライドできる非抽象メソッドをフックメソッド
と呼ぶ。
フックメソッドを呼ぶことで、具象クラスは以下を選ぶことができる。
- 基底実装をオーバーライドすることで別の処理を実行させる
- 標準実装をそのまま使用する
基底クラスの実装は以下のようになる。
class Report
~~省略~~
def output_start
end
def output_head
raise 'Called abstract method: output_head'
end
def output_body_start
end
def output_line
raise 'Called abstract method: output_line'
end
def output_body_end
end
def output_end
end
end
Discussion