LoginSignup
9
9

More than 5 years have passed since last update.

Template Method パターン

Last updated at Posted at 2014-07-22

なんとなくは理解しているけど、きちんと把握していなかったのでまとめてみる

「振る舞いに関するパターン」に属する。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 パターン に続く

9
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
9