Ruby
programming
デザインパターン

Rubyによるデザインパターン【Template Method】-テンプレは準備した、あとはお好きに-

More than 3 years have passed since last update.


概要

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分で処理を分けている。

フォーマットがさらに増えたらどうなるのか・・

ということで、解決策を考える。


パターンの適用によるリファクタリング


変わるものと変わらないものを分離する


  • 基本的な処理の流れ・テンプレート(変わらないもの)をフォーマット(変わるもの)に依存させるべきではない。

  • 前者は抽象的な基底クラスに、後者は具象サブクラスに定義する。


つまり

「基本的な処理の流れ」である、


  1. 特定フォーマットに必要なヘッダ情報を出力する。

  2. タイトルを出力する。

  3. レポート本体(body)を出力する。

  4. フォーマットに要求される残りの要素を出力する。

これらを「テンプレ」として抽象基底クラスに定義し、残りはサブクラスへ切り出す。


実装


抽象基底クラス

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