Update 2023.11.18 2017.05.05

Python デザインパターン サンプルコード Template Method
結城 浩「Java言語で学ぶデザインパターン入門」をPython化
Python3(3.11)で動くソースコード(.pyファイル .ipynbファイル)あります
「anaconda3」on .py「PyCharm」.ipynb「Jupyter Notebook」

◆◆Template Method パターンの使われる場面◆◆

テンプレートとは型板のことだが転じて定型書式のことを指すことが多い。似たような作業例えば「肉を料理する,魚を料理する」「テキスト文書を処理する,ワード文書を処理する」「ディスプレイに出力する,プリンタに出力する」などということを実装するには個々に実装してもかまわないが,それでは似た名称のメソッドがたくさんできるし,メソッドを使う側も迷う。また,人によってさまざまな実装になってしまう。

これを改善するのがテンプレートである。GoFの23のデザインパターンにも何回も出てくるように基本中の基本である。


(2023-11-18)Python3.11で動作確認済み


◆◆Template Method パターンとは◆◆

GoFによれば,Template Method パターンの目的は, 「1つのオペレーションにアルゴリズムのスケルトンを定義しておき,その中のいくつかのステップについては,サブクラスでの定義に任せることにする。Template Method パターンでは,アルゴリズムの構造を変えずに,アルゴリズム中のあるステップをサブクラスで再定義する。」

GoFによれば,Template Method パターンは次のような場合に利用する。
・アルゴリズムの不変な部分をまず実装し,振る舞いが変わり得る部分の実装はサブクラスに残しておく場合。
・同じコードがいたるところに現れることがないように,サブクラス間で共通の振る舞いをする部分は抜き出して,これを共通のクラスに局所化する場合。これは,Opdyke と Johnson による“一般化のためのリファクタリング”[OJ93]の良い例である。まず,既存のコードにおける相違点を識別し,次にその相違点を新しいオペレーションに分離する。最後に,既存のコードを,その相違点については新しいオペレーションを呼び出すようにした template method で置き換える。
・サブクラスの拡張を制御する場合。特定の時点で“hook”operation(「結果」の節を参照)を呼び出すテンプレートメソッドを定義することができる。それにより,このポイントでのみ拡張が許されることになる。

GoFによれば,関連するパターンは次のようなものである。
Factory Method パターン: しばしば template method により呼び出される。「動機」の節の例では,factory method である DoCreateDocument オペレーションが,template method である OpenDocument オペレーションにより呼び出されている。
Strategy パターン: template method では,アルゴリズムの一部を変更するために継承を利用している。それに対して Strategy パターンでは,アルゴリズム全体を変更するために委譲を利用している。

◆◆Template Method パターンのサンプルのクラス構成◆◆

ソースコードは巻末にあり,ソースファイルはこのWebの左上隅にありダウンロードできます。
                    AbstractDisplay
            (open, print, close, display)
             ↑                       ↑
         CharDisplay               StringDisplay
    (open, print, close)    (open, print, close, printLine)

サンプルは,文字または文字列を繰り返し,その周りに飾りを付けるものです。文字と文字列を同じ扱いにしています。またそれの飾りも同じ扱いにします。

class AbstractDisplay は,テンプレートであり,抽象クラスです。
class CharDisplay は,1文字を扱う具象クラスです。
class StringDisplay は,文字列を扱う具象クラスです。

◆◆抽象クラス・抽象メソッドの Java との違い◆◆

Javaでは抽象クラスの抽象メソッドを使う。何もしない抽象メソッドが何の役に立つのかといぶかしく思ったものだが,テンプレートとして使えるのである。Javaの言語仕様としてサブクラスで必ず具象化してオーバーライドしなければならない。

Pythonでは言語仕様として抽象クラスや抽象メソッドは用意されていない。しかし,何もしないメソッドを書くことはできるし,それをオーバーライドすることで具象化もできる。Pythonでは言語仕様として抽象クラス・抽象メソッドがないので具象化を義務づけられていないが,添付ソースのようにデコレータ( @abstractmethod)で Java と同様なことができるようになっている。

◆◆Template Method パターンの実装◆◆

「class AbstractDisplay」の何もしないメソッドがテンプレートです。ステップとして「open」「print」「close」がある。

そのステップにより実現するのが「display」である。サンプルなので単純にしてあるが,「open」で先頭の囲みを,「close」で末尾の囲みを,「print」で指定文字/指定文を5回繰り返す。

具象化するのは「class CharDisplay」「class StringDisplay」のサブクラスであり「class AbstractDisplay」をスーパークラスとしている。前者は,囲みと指定文字を1行に書いて終わり,後者は囲みと指定文それぞれを1行に書く。

全角を半角2文字と数えるためのサブルーチンは Java サンプルにはないものである。全角文字がなければコメントアウトしてある組込関数「len()」を使えばよい。

「display」はサブクラスで実装していないので,サブクラスのインスタンスから呼び出してもスーパークラスのメソッドが呼び出されます。このことは抽象メソッドとは機能が違いますが,継承の重要なポイントです。

◆◆ソースコード◆◆

このWebページの左上隅からダウンロードできます。

ソースファイルは1つです。
・TemplateMethod.py;Template Method パターンのPythonサンプル

【TemplateMethod.py】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from abc import ABCMeta, abstractmethod
import unicodedata

class AbstractDisplay(metaclass = ABCMeta):

    @abstractmethod
    def open(self):
        pass
    @abstractmethod
    def print(self):
        pass
    @abstractmethod
    def close(self):
        pass
    def display(self):
        self.open()
        for i in range(5):
            self.print()
        self.close()

class CharDisplay(AbstractDisplay):
    def __init__(self, ch):
        self.ch = ch
    def open(self):
        sys.stdout.write("<<")
    def print(self):
        sys.stdout.write(self.ch)
    def close(self):
        sys.stdout.write(">>\n")

class StringDisplay(AbstractDisplay):
    def __init__(self, string):
        self.string = string
        #self.width = len(self.string)  # 全角を正しく数えられない
        def str_len(str):
            # 全角を半角2コ分に数える
            length = 0
            for ch in str:
                ch_width = unicodedata.east_asian_width(ch)
                if ch_width in "WFA":
                    length += 2         #戻り値:WとFとAが全角
                else:
                    length += 1         #戻り値:NaとHが半角
            return length
        self.width = str_len(self.string) # 全角を正しく数えられる
    def open(self):
        self.printLine()
    def print(self):
        sys.stdout.write("|{0}|\n".format(self.string))
    def close(self):
        self.printLine()
    def printLine(self):
        sys.stdout.write("".join(("+","-"*self.width,"+\n")))

def main():
    d1 = CharDisplay("H")
    d2 = StringDisplay("Hello, world.")
    d3 = StringDisplay("こんにちは。")
    d1.display()
    d2.display()
    d3.display()

if __name__=='__main__': # このファイルが起動されたときのみ最初に実行される
    main()
"""
<<HHHHH>>
+-------------+
|Hello, world.|
|Hello, world.|
|Hello, world.|
|Hello, world.|
|Hello, world.|
+-------------+
+------------+
|こんにちは。|
|こんにちは。|
|こんにちは。|
|こんにちは。|
|こんにちは。|
+------------+
"""

以上

トップページに戻る
inserted by FC2 system