LoginSignup
70
67

More than 5 years have passed since last update.

Pythonでデザインパターン TemplateMethodパターン

Last updated at Posted at 2018-01-10

pythonとデザインパターンの学習のため、記事をまとめようと思いました。
デザインパターンの最初はTemplateパターンです。

環境

python3

参考

@kidach1 さん、まるパクリさせてもらいますm(__)m(ソースのみ修正 ruby→python)
参考「Rubyによるデザインパターン」【Template Method】-テンプレは準備した、あとはお好きに-
https://qiita.com/kidach1/items/7c2a80bfc8a87a05051f
この記事はPythonです

どんなパターンか

ソフトウェアには変化がつきものである。
その変化の度に、広範囲な修正が入っているようではとても対応できない。

そこで、
抽象的な処理と具体的な処理を分離することで、
変化に強い構造を実現したい。

そのための手段の一つがTemplate Method。

骨子は以下2種のクラスの利用。

骨格となるメソッドを持った抽象基底クラス(テンプレートクラス)
処理の詳細を詰める具象クラス(サブクラス)

ひどいコード

あるレポートを様々なフォーマット(HTMLやPlainText)で出力するReportクラス

template.py
#!/usr/bin/python
# coding: utf-8 

class Report:  
  def __init__(self):
    self.title = '月次報告'
    self.text = ['最高!', '順調', '普通']

  def output_report(self, format):
    if ( format == 'plain' ):
      print("*** "+ self.title + " ***")
    elif (format == 'html'):
      print('<html>')
      print('  <head>')
      print("    <title>"+ self.title +"</title>")
      print('  </head>')
      print('  <body>')
    else:
      raise ValueError("Unknown format")

    for elem in self.text:
      if( format == 'plain'):
        print(elem)
      else:
        print("    <p>"+ elem +"</p>")

    if (format == 'html'):
      print('  </body>')
      print('</html>')

report = Report()
report.output_report("plain")
report.output_report("html")

フォーマットがHTMLなのかPlainTextなのかによって、
都度if分で処理を分けている。
フォーマットがさらに増えたらどうなるのか・・

ということで、解決策を考える。

パターンの適用によるリファクタリング

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

基本的な処理の流れ
・テンプレート(変わらないもの)をフォーマット(変わるもの)に依存させるべきではない。
・前者は抽象的な基底クラスに、後者は具象サブクラスに定義する。

つまり

「基本的な処理の流れ」である、

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

これらを「テンプレ」として抽象基底クラスに定義し、残りはサブクラスへ切り出す。

実装

抽象基底クラス

class Report:
  def __init__(self):
    self.title = '月次報告'
    self.text = ['最高!', '順調', '普通']

  def output_report(self):
    self.output_start()
    self.output_title()
    self.output_body_start()
    for line in self.text:
      self.output_line(line)
    self.output_body_end()
    self.output_end()

  def output_start(self):
    pass

  def output_title(self):
    self.output_line(self.title)

  def output_body_start(self):
    pass

  def output_line(self, line):
    raise 'Called abstract method: output_line'

  def output_body_end(self):
    pass

  def output_end(self):
    pass

output_start, output_title, output_body_start, output_body_end, output_endのように
具象クラスによってオーバーライドできる(不要ならしなくてもよい)メソッドのことを
フックメソッドと呼ぶ。

一方でoutput_lineは、直接呼び出すと例外を発生させる。つまり、
具象クラスによるオーバーライドを強要している。

→output_lineは、「各フォーマットごとに処理が異なる=より変化しやすい処理」
という想定があるため。

具象サブクラス1(フォーマット:HTML)

class HTMLReport(Report): 
  def output_start(self):
    print('<html>')

  def output_title(self):
    print('  <head>')
    print("    <title>"+ self.title + "</title>")
    print('  </head>')

  def output_body_start(self):
    print('  <body>')

  def output_line(self, line):
    print("    <p>"+ line +"</p>")

  def output_body_end(self):
    print('  </body>')

  def output_end(self):
    print('</html>')

html_report = HTMLReport()
html_report.output_report()

#<html>
#  <head>
#    <title>月次報告</title>
#  </head>
#  <body>
#    <p>最高!</p>
#    <p>順調</p>
#    <p>普通</p>
#  </body>
#</html>

具象サブクラス2(フォーマット:PlainText)

class PlainTextReport(Report):
  def output_title(self):
    print("*** "+ self.title + " ***")

  def output_line(self, line):
    print(line)

plain_repot = PlainTextReport()
plain_repot.output_report()

#*** 月次報告 ***
#最高!
#順調
#普通

まとめ

TemplateMethodパターンにより、

変わるもの(具象サブクラス)と変わらないもの(テンプレート)を分離して、変化に強い構造へ。

kidach1さんまるパクリですみませんm(__)m

70
67
2

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
70
67