🍣

Rubyによるデザインパターン~Template Method~

2023/03/08に公開約3,300字

概要

以下で書いた一般的な原則編の続き。
https://zenn.dev/nabe3/articles/a3ef2ecb266a2a

ラスオルセン著「Rubyによるデザインパターン」の第三章Template Method
を読んで整理していく。

例題

月次のレポートを出力する処理の例

ごちゃごちゃしていて可読性が低く、フォーマットが増える度に既存の実装を修正する必要があり、フォーマットが壊れるリスクがある。

class Report
  def initialize
    @title = 'monthly report'
    @text = ['going well', 'great']
  end
 	
  def output_report(format)
    if format == :plain
      puts("*** #{@title} ***")
    elseif 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

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

処理は以下で構成されている。

  1. 特定フォーマットに必要なヘッダ情報を出力する
  2. タイトルを出力する
  3. レポート本体の各行を出力する
  4. フォーマットに要求される残りの要素を出力する

抽象基底クラスを定義して、基本的なステップを実行する主となるメソッドをもたせ、ステップの詳細はサブクラスに持たせる

出力フォーマットごとにサブクラスを作成する

クラス図で表すと以下のような感じ。

Rubyでは抽象メソッド抽象クラスを持たないため、抽象メソッドを呼び出そうとしたとき以下のように例外を投げる。

class Report
  def initialize
    @title = 'monthly report'
    @text = ['going well', 'great']
  end
 	
  def output_report
    output_start
    output_head
    output_body_start
    output_body
    output_body_end
    output_end
  end
 	
  def output_body
    @text.each do |line|
      output_line(line)
    end
  end
 	
  def output_start
    raise 'Called abstract method: output_start'
  end
 	
  def output_head
    raise 'Called abstract method: output_head'
  end
   	
  def output_body_start
    raise 'Called abstract method: output_body_start'
  end
	
  def output_line
    raise 'Called abstract method: output_line'
  end
 	
  def output_body_end
    raise 'Called abstract method: output_body_end'
  end
   	
  def output_end
    raise 'Called abstract method: output_end'
  end
end

Report クラスのサブクラスは以下の通り。

 # フォーマットがHTMLの場合
class HTMLReport < Report
  def output_report
    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
 
# フォーマットがtextの場合
class PlainTextReport < Report
  def output_start
  end
 	
  def output_head
    puts("***#{@title}***")
    puts
  end
 	
  def output_body_start
  end
 	
  def output_line(line)
    puts(line)
  end
		
  def output_body_end
  end
		
  def output_end
  end
 end

フックメソッド

PlainTextReportを見るとテキストでは不要にも関わらずoutput_startoutput_endがオーバーライドされている。
このような場合は、基底クラス側で標準となる実装を提供するほうが合理的。

Template Methodの具象クラスでオーバーライドできる非抽象メソッドをフックメソッドと呼ぶ。

フックメソッドを呼ぶことで、具象クラスは以下を選ぶことができる。

  • 基底実装をオーバーライドすることで別の処理を実行させる
  • 標準実装をそのまま使用する

基底クラスの実装は以下のようになる。

class Report
  ~~省略~~
 	
  def output_start
  end
 	
  def output_head
    raise 'Called abstract method: output_head'
  end
   	
  def output_body_start
  end
	
  def output_line
    raise 'Called abstract method: output_line'
  end
 	
  def output_body_end
  end
   	
  def output_end
  end
end

Discussion

ログインするとコメントできます