Rubyでデザインパターン。Template Methodパターン。Design Pattern in Ruby

2018年9月2日

Rubyでのデザインパターン

デザインパターンといえば、こちらの本

増補改訂版Java言語で学ぶデザインパターン入門

が有名で大変ためになる本ですが、これらのデザインパターンをrubyで書こうとするとどうなるのかというのでこちら購入して読んでみました。

はい、英語です。日本語版もあるのですが、6000円ほどで結構高かったり英語の技術書を読破するという今年の目標もあったので、こちらを選びました。

 

TemplateMethodパターン

 

早速デザインパターンの紹介にうつります。第一回目はTemplateMethodパターンです。

 


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

先にコードをのせてしまいましたが、Template Methodパターンではこのような抽象クラスを定義して、このReportクラスのようなクラスを継承した具象クラスでメソッドをオーバーライドして実装していきます。

このデザインパターンのミソはこのコードでいうとouput_reportメソッドになります。このメソッド内で実行する処理の順番などを定義して、そこに定義された分割されたメソッドの実際の挙動はサブクラスで定義していくというような実装になります。

サブクラスでは、サブクラスでそのサブクラス特有の処理だけ実装すればよくなる(output_reportのようなメソッドは共通処理なので抽象クラスに実装しておくだけですむ)ので、全体としてコードの重複をさけて記述量を減らすことができます。

 

Template Methodで適用する例

 

先ほど紹介したデザインパターンの例を引用しますが、ある日月次のレポートとしてHTMLでのレポートを作成する必要がありそれを実装することになった場合を考えます。

 

class Report
  def initialize
   title = 'Monthly Report'
   @text = [ 'Things are going', 'really, really well.' ]
  end

  def output_report
    puts('<html>')
    puts('  <heade>')
    puts('    <title>#{@title}</title>')
    puts('  </head>')
    puts('  <body>')
    @text.eah do |line|
      puts('    <p>#{line}</p>')
    end
    puts('   </body>')
    puts('</html>')
  end
  
end

HTMLを出力するだけなので、このよな実装をしたとします。しかし、後からテキスト形式でも出してほしいといったときにこまってしまいます。

ここで付け焼き刃的な実装をするとこんな感じになってしまいます。

class Report
  def initialize
    @title = 'Monthly Report'
    @text = ['Things are going', 'really, really well']
  end
    
  def output_report(format)
    if format == :plain
      puts('*** #{@title{} ***')
    elsif format == :html
      puts('<html>')
      puts('  <heade>')
      puts('    <title>#{@title}</title>')
      puts('  </head>')
      puts('  <body>')
    else
      raise "Unknown format: #{format}"
    end

    @text.eah do |line|
    if format == :plain  
        puts('    <p>#{line}</p>')
      else
        puts('    <p>#{line}</p>')
      end
    end
    puts('   </body>')
    puts('</html>')
  end
end

 

うーん、動きはしそうですが、if文が多くてなかなか読みづらいですね。しかも、こんどは別のcsv形式で出力してほしいとなった場合にまたif文が増えて辛くなりそうな気しかしないです。

こういうときにTemplate Methodパターンで実装をすると、一番最初に紹介したReportクラスを継承する形でHTMLReportクラスとTextReportクラスを実装するとだいぶ楽になります。

 

HTMLReport.rb

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

TextReport

class TextReport < Report
  
  def output_head
    puts("*** #{@title} ***")
    puts
  end
    
  def output_line(line)
    puts(line)
  end
  
end

このようにそれぞれの出力フォーマットをReportクラスのサブクラスとして切り出すことによってすっきりとしたコードになりました。

これらのクラスを使う場合は、出力したいフォーマットにあわせて

 

# HTML出力する場合
HTMLReport.new.output_report

# Text出力する場合
TextReport.new.output_report

 

まとめ

 

ここまでで、TemplateMethodパターンの紹介は以上です。デザインパターンはなかなか覚えるのに時間かかかるのですが、実装前の段階でデザインパターンを知っているか知らないかで実装の簡潔さや変更の容易さがかわってきて知っていると本当に便利です。

最初にもちょっと触れましたが、TemplateMethodパターンの肝は抽象クラスのそれぞれのメソッドを呼び出す親メソッド(Reportクラスのoutput_reportメソッド)サブクラスでの実装を強制するメソッド
です。

親メソッドというかテンプレートメソッドどういう順番で個別のメソッドを呼び出すかというのを定義して、それに合わせてサブクラスでメソッドをオーバライドしていくので当然このテンプレートメソッドがクラス設計の重要な部分になってきます。設計時にはどういうパターンが今後の変更などを加味してどういうメソッドにするのかというのを決めていきましょう。

また、Reportクラスではoutput_lineではメソッド内で例外をなげるように実装することで子クラスでオーバーライドされない場合にはエラーで処理を止めるように実装しています。一方その他のメソッドはデフォルトで返却する値(もしくは何もしない)を決めているのでサブクラスで固有の処理がない場合はそのままReportクラスのメソッドを実行するようにして、コードの重複を減らしています。

この大きくわけて二つのテンプレートメソッドと、オーバライド必須のメソッドを覚えておけばわりとするっと実装できるのが TemplateMethodパターンです。

ぜひ実務や個人開発でもTemplateMethodパターンが適用できないかどうか考えて実装してきれいなコード書いていきましょう。