概要
Rubyによるデザインパターン第3章。
Template Method Pattern。
Rubyによるデザインパターン5原則に則って理解する。
どんなパターンか
ソフトウェアには変化がつきものである。
その変化の度に、広範囲な修正が入っているようではとても対応できない。
そこで、
抽象的な処理と具体的な処理を分離することで、
変化に強い構造を実現したい。
そのための手段の一つがTemplate Method。
骨子は以下2種のクラスの利用。
- 骨格となるメソッドを持った抽象基底クラス(テンプレートクラス)
- 処理の詳細を詰める具象クラス(サブクラス)
ひどいコード
あるレポートを様々なフォーマット(HTMLやPlainText)で出力するReportクラス
class Report
def initialize
@title = '月次報告'
@text = ['最高!', '順調', '普通']
end
def output_report(format)
if format == :plain
puts("*** #{@title} ***")
elsif 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
フォーマットがHTMLなのかPlainTextなのかによって、
都度if分で処理を分けている。
フォーマットがさらに増えたらどうなるのか・・
ということで、解決策を考える。
パターンの適用によるリファクタリング
変わるものと変わらないものを分離する
- 基本的な処理の流れ・テンプレート(変わらないもの)をフォーマット(変わるもの)に依存させるべきではない。
- 前者は抽象的な基底クラスに、後者は具象サブクラスに定義する。
つまり
「基本的な処理の流れ」である、
- 特定フォーマットに必要なヘッダ情報を出力する。
- タイトルを出力する。
- レポート本体(body)を出力する。
- フォーマットに要求される残りの要素を出力する。
これらを「テンプレ」として抽象基底クラスに定義し、残りはサブクラスへ切り出す。
実装
抽象基底クラス
class Report
def initialize
@title = '月次報告'
@text = ['最高!', '順調', '普通']
end
def output_report
output_start
output_title
output_body_start
@text.each do |line|
output_line(line)
end
output_body_end
output_end
end
def output_start
end
def output_title
output_line(@title)
end
def output_body_start
end
def output_line(line)
raise 'Called abstract method: output_line'
end
def output_body_end
end
def output_end
end
end
output_start, output_title, output_body_start, output_body_end, output_endのように
***具象クラスによってオーバーライドできる(不要ならしなくてもよい)***メソッドのことを
フックメソッドと呼ぶ。
一方でoutput_lineは、直接呼び出すと例外を発生させる。つまり、
具象クラスによるオーバーライドを強要している。
→output_lineは、「各フォーマットごとに処理が異なる=より変化しやすい処理」
という想定があるため。
具象サブクラス1(フォーマット:HTML)
class HTMLReport < Report
def output_start
puts('<html>')
end
def output_title
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
具象サブクラス2(フォーマット:PlainText)
class PlainTextReport < Report
def output_title
puts("*** #{@title} ***")
end
def output_line(line)
puts(line)
end
end
クラスの利用
[15] pry(main)> report = HTMLReport.new
[16] pry(main)> report.output_report
<html>
<head>
<title>月次報告</title>
</head>
<body>
<p>最高!</p>
<p>順調</p>
<p>普通</p>
</body>
</html>
[19] pry(main)> report = PlainTextReport.new
[20] pry(main)> report.output_report
*** 月次報告 ***
最高!
順調
普通
まとめ
TemplateMethodパターンにより、
変わるもの(具象サブクラス)と変わらないもの(テンプレート)を分離して、変化に強い構造へ。
以下へ続く
【Strategy】-取り替え可能パーツ群を戦略的に利用せよ-
http://qiita.com/kidachi_/items/02f8f6df955ba1d0e93b