なんとなくは理解しているけど、きちんと把握していなかったのでまとめてみる
「振る舞いに関するパターン」に属する。Template Method パターンの目的は、ある処理のおおまかなアルゴリズムをあらかじめ決めておいて、そのアルゴリズムの具体的な設計をサブクラスに任せることである。そのため、システムのフレームワークを構築するための手段としてよく活用される。
つかいみち
- 重複した実装の洗練・整理
- 流れは一緒だけれども、内部の処理が異なる実装において、流れと、内部の処理の実装を分けることができる
- 個別の処理の実装だけで、機能が追加できる
具体的な使い道
- 複数のフォーマットでの結果の出力(TXT、HTML、CSV)
- 複数のプロトコルでのダウンロード処理(HTTP、FTP、SCP等)
実装例
配列を受け取って、HTMLやプレーンテキストで出力する class を作ってみる
- StringLister : 基底 class
- PlainTextStringLister : プレーンテキストで出力
- HtmlStringLister : HTMLで出力
factory_method.rb
# 基底class
#
class StringLister
attr_reader :items
def initialize(items)
@items = items
end
def header; end
def body(item)
# ユーザ定義の基底クラスにおいて、
# 抽象メソッドが派生クラスでオーバライドされることを要求する場合、
# この例外を送出しなくてはなりません。
#
raise NotImplementedError
end
def footer; end
# ヘッダ、本文、フッタの順に出力する
def display
result = []
result << header
items.each{|item| result << body(item)}
result << footer
result.compact.join("\n")
end
end
class PlainTextStringLister < StringLister
def body(item)
item
end
end
class HtmlStringLister < StringLister
def header
'<html><body>'
end
def body(item)
"<div>#{item}</div>"
end
def footer
'<html><body>'
end
end
items = %w[abc def ghi]
puts PlainTextStringLister.new(items).display
# abc
# def
# ghi
puts HtmlStringLister.new(items).display
# <html><body>
# <div>abc</div>
# <div>def</div>
# <div>ghi</div>
# <html><body>
- #initialize、#display といった、処理の流れの部分は基底classで実装されているため、
PlainTextStringLister、HtmlStringLister では、個別の実装に集中する事ができる- PlainTextStringLister に至っては、#body の実装のみで済んだ
- この後、CSV出力が必要になっても、StringListerを継承するだけで、最低限の実装で実現できる
第一級関数を使用した Template Method パターン
StringLister のサブクラスを作るのではなく、実行するメソッドを引数で渡す方法
関数を変数にできる Ruby っぽいやり方
柔軟に実装するならばこっちの方が良いと思う
ただ、これってどちらかと言うと、Strategy パターン になる?
template_method.rb
# 基底class
#
class StringLister
attr_reader :items
def initialize(items, options={}, &body)
@items = items
@header = options[:header] if options.has_key?(:header)
@body = body
@footer = options[:footer] if options.has_key?(:footer)
end
# ヘッダ、本文、フッタの順に出力する
def display
result = []
result << @header.call if @header
items.each{|item| result << @body.call(item)}
result << @footer.call if @footer
result.compact.join("\n")
end
end
items = %w[abc def ghi]
disp = StringLister.new(items) do |item|
item
end.display
puts disp
disp = StringLister.new(items,
:header => ->(){ "<html></body>" },
:footer => ->(){ "</body></html>" }
) do |item|
"<div>#{item}</div>"
end.display
puts disp
Strategy パターン との違い
- Template Method パターン : 処理をサブクラスに任せるパターン
- Strategy パターン : 処理を移譲するパターン
参考
→ Factory Method パターン に続く
→ Strategy パターン に続く