読者です 読者をやめる 読者になる 読者になる

MogLog

メモというか日記というか備忘録というか

『Rubyによるデザインパターン』学習ノート:Template Method

Template Method
アルゴリズムに多様性を持たせたい場合に役に立つ。
基底クラスには、不変の部分を記述し、変わる部分はサブラクスに定義するメソッドにカプセル化する。
基底クラスは、メソッドを未定義にしておくことができる。ただしその場合は、サブクラスでそのメソッドを提供しなくてはならない。また、未定義にするかわりに、基底クラスで標準実装し(フックメソッド)、必要な場合のみサブクラスでオーバーライドさせることもできる。

【重要】
変化するものを変わらないものから分離する
変わらないもの=テンプレートメソッドに記述された基本的なアルゴリズム
変化するもの=サブクラスで提供される詳細な処理

【語句】
○基底クラス
あるクラスの仕様を継承して、新しいクラスを作る際に、継承元となるクラスのこと。親クラス。

○抽象クラス(Abstract Class)
クラス構造の雛形を定義する目的だけのために作られたクラスのこと。具体的な処理は、これを継承したクラス(サブクラス)に記述させる。抽象クラスの存在意義は、複数のクラスにたいして共通性を持たせることであり、クラス設計においてとても重要な役割を担う。
テンプレートメソッドパターンで言えば、変わらない処理をまとめるためのクラス。
サブクラスが共通して持つメソッドを抽象的に持つクラスのことと言える。

○具象クラス(Concrete Class)
抽象クラスではない、通常のクラス。

○メソッドのオーバーライド
サブクラス側でスーパクラスと同じ名前を持つメソッドを定義すること。サブクラスで定義したメソッドが優先される。オーバーライドされた場合であっても、スーパークラス側のメソッドを呼び出すことは可能。

○フックメソッド
Template Methodの具象クラスによってオーバーライドできる非抽象メソッドのことをフックメソッドと呼ぶ。
フックメソッドを使うと、具象クラスは、①基底実装をオーバーライドすることで別の処理を実行させるか、②標準実装をそのまま使うかを選択することができる。

○ダックタイピング
「いま私であるものこそが私なのだ」という型に対するアプローチ。
If it walks like a duck and quacks like a duck, it must be a duck.
どのクラスからのインスタンスなのかなど気にしない、重要なのは「そのインスタンスはなにができるのか」というアプローチ。

○サブクラス = 子クラス = 派生クラス

○親クラス = 基底クラス
今まで読んだ書籍を思い出す限り、Rubyではサブクラスと使うのが多いようだ。


【サンプルコード】

# -*- encoding: utf-8 -*-

# 抽象基底クラス
class Report

  def initialize
    @title = '月次報告'
    @text = ['順調', '最高の調子']
  end

  # このメソッドはオーバーライドされない!
  # このメソッドがサブクラスがオーバーライドするメソッドに
  # よって行われる具体的な処理をまとめて出力している。
  # つまり、これがテンプレートメソッドにあたる。
  def output_report
    output_start
    output_head
    output_body_start
    output_body
    output_body_end
    output_end
  end

  # 実際に処理をおこなっていないメソッドをなぜわざわざ記述し、オーバーライドさせる必要があるのだろうか?
  # → 抽象基底クラスであるこのReportクラス内に、形だけでもこれらのメソッドがないと、上のテンプレートメソッド内で
  # No Method Errorが起きてしまうためではないだろうか。あとは、インターフェイスを近づけるためとか?
  def output_body
    @text.each do |line|
      output_line(line)
    end
  end

  # このメソッドのように、なにも記述されていないメソッドはサブクラスの利便性向上の為にこうなっている
  # PlainTextReportクラスでは、ここの処理が全く必要ないため。
  # このようにしないと、PlainTextReportクラスになにもしないメソッドを書かなくてはならなくなる
  # こういったメソッドを「フックメソッド」と呼ぶ。
  def output_start
  end

  def output_head
    output_line(@title)
  end

  # フックメソッド
  def output_body_start
  end

  # これもフックメソッド
  def output_line(line)
    raise 'Called abstarct method: output_line'
  end

  # フックメソッド
  def output_body_end
  end

  # フックメソッド
  def output_end
  end

end


# 具象サブクラス
class HTMLReport < Report
  
  def output_start
    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


# 具象サブクラス
class PlainTextReport < Report

  def output_head
    puts "**** #{@title} ****"
  end

  def output_line(line)
    puts line
  end

end

report = HTMLReport.new
report.output_report

report = PlainTextReport.new
report.output_report


【出力結果】

<html>
<head>
<title>月次報告</title>
<head>
<body>
<p>順調</p>
<p>最高の調子</p>
</body>
</html>
**** 月次報告 ****
順調
最高の調子