Edited at

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

More than 1 year has passed since last update.

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