Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
41
Help us understand the problem. What is going on with this article?

More than 5 years have passed since last update.

@kidach1

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

概要

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

41
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
41
Help us understand the problem. What is going on with this article?