デザインパターン(Design Pattern) #Template Method


設計を意識したコードが書けるようになる為に、デザインパターン修行しました。
他のDesign Patternもちょくちょく出していきます。

前置き

デザインパターンをどういう時に、何を、どう使うのかを理解することが一先ずの目標。
(Javaというか静的型付言語は初めてで、且つpython歴もそんなに長くないので、Pythonistaぽっくないところがあると思います。ご指摘ございましたらご教授ください。)

今回は、振る舞いに関するパターンのTemplate Method。

Template Methodとは

スーパークラスで処理の枠組みを定め、サブクラスでその具体的内容を定めるようなデザインパターン
か。つまり、スーパークラスのテンプレートメソッドでアルゴリズムが記述されていますので、サブクラス側では アルゴリズムをいちいち記述する必要がなくなる。そして、Template Methodパターンでプログラミングしていれば、テンプレートメソッドに誤りが発見された場合でも、テンプレートメソッドさえ修正すればよいというその後の修正の手間を抑えることができる。

つまり、おおまかなアルゴリズムをあらかじめスーパークラスで決めて、アルゴリズムの具体的な設計と処理はサブクラスで行うことかな。

概要

ここで作るサンプルプログラムは、「文字や文字列を5回繰り返して」表示しています。
ここでは、AbstractDisplay, CharDisplay, StringDisplayの3つのクラス, Main(実行ファイル)の構成になってます。
AbstractDisplayクラスではdisplayメソッドが定義されています。
そしてdisplayメソッドの中では、opening, printing, closingという3つのメソッドが使われています。これら3つのメソッドもAbstractDisplayクラスの中で宣言されていますが、実体がない抽象メソッドになっています。ここでは、抽象メソッドを使っているdisplayメソッドがテンプレートメソッドになります。
open, print, closeメソッドを実際に実装してるのは、AbstractDisplayクラスのサブクラスであるCharDisplayクラスやStringDisplayクラスです。下記に図があります。

abstract_display.py
from abc import ABCMeta, abstractmethod

class AbstractDisplay(metaclass=ABCMeta):
    @abstractmethod 
    def opening(self):
        pass

    @abstractmethod     
    def printing(self):
        pass

    @abstractmethod 
    def closing(self):
        pass

    def display(self):
        self.opening()
        for i in range(5):
            self.printing()
        self.closing()
char_display.py
import sys
from abstract_display import AbstractDisplay

class CharDisplay(AbstractDisply):
    def __init__(self, ch):
        self.__ch  = ch

    def opening(self):
        sys.stdout.write('<<') # printを使うと出力直後に空白が一文字出てしまうので
    def printing(self):
        sys.stdout.write(self.__ch)

    def closing(self):
        print('>>')   
char_display.py
import sys
from abstract_display import AbstractDisplay

class StringDisplay(AbstractDisplay):
    def __init__(self, string):
        self.__string = string
        self.__width = len(string)

    def opening(self):
        self.__print_line()

    def printing(self):
        self.print('|' + self.__string + '|')

    def closing(self):
        self.__print_line() 

    def __print_line(self):
        sys.stdout.write(''.join(('+', '-' * self.__width, '+\n')))  # printを使うと出力直後に空白が一文字出てしまうので
main.py
from char_display import CharDisplay
from string_display import StringDisplay


def main():
    d1 = CharDisplay('H')
    d2 = StringDisplay('Hello, World')
    d3 = StringDisplay('こんにちわ')

    d1.display()
    d2.display()
    d3.display()

if __name__ == '__main__':
    main()

python main.py

<<HHHHH>>
+------------+
|Hello, World|
|Hello, World|
|Hello, World|
|Hello, World|
|Hello, World|
+------------+
+-----+
|こんにちわ|
|こんにちわ|
|こんにちわ|
|こんにちわ|
|こんにちわ|
+-----+

抽象クラスの意義とは

実際の処理内容はサブクラスでなければ分からないが、抽象クラスの段階で処理の流れを形作るのは大切なことらしい。
抽象クラスがあると、サブクラスが抽象クラスにあるメソッドを実装する。
そしてサブクラスに対して、メソッドの実装の責任を負わせる。

まとめ

抽象クラスはインスタンスを作ることができない。
インスタンスを作れないクラスなど何のために存在するのかと思ったが、抽象メソッドにはメソッドの本体が書かれていないので、具体的な処理内容はわかりません。しかし、メソッドの名前を決め、そのメソッドを使ったテンプレートメソッドによって処理を記述することはできるし、処理の共通化を行うことに拠って修正やバグの可能性を減らすことができる。

デザインパターンは関心の分離が根底にある気がする。

参考