さるへい備忘録

さるへいのやったことを綴っているブログです。基本的にテクノロジーの話題です。

Pythonで学ぶデザインパターン入門Prototypeパターン

結城先生の Java言語で学ぶデザインパターン入門 のコードを Python に書き直していくシリーズ第6段です。

Prototypeパターンとは

今回もインスタンス生成に関するパターンです。
ざっくり説明すると、インスタンスの雛形をどこかに保持しておき、それを元に新しいインスタンスを作成するパターンですね。
コピーのような形で作成するのがこのデザインパターンの特徴です。

ソースコード

コードの概要

文字列を渡すと、特定の装飾をした文字列が出力されるといったコードになります。
たとえば、 Hello と渡したら、 *Hello* のように装飾されるといったコードです。
例の * のような装飾の文字が可変になる仕組みになっております。

今回は Manager というクラスで、 Product というクラスを継承したインスタンスをcloneしていきます。
つまり、 Managerインスタンスを保持しておけば好きな時にcloneできるといった仕組みです。

ソースコード

Product

# coding: utf-8
from abc import ABCMeta, abstractmethod


class Product(metaclass=ABCMeta):

    @abstractmethod
    def use(self, s: str):
        pass

    @abstractmethod
    def create_clone(self):
        pass

このデザインパターンで利用する、cloneするインスタンスの抽象クラスです。
後術の Manager クラスで create_clone メソッドを呼び出して複製します。

Manager

# coding: utf-8
from prototype.framework.product import Product


class Manager(object):

    def __init__(self):
        self.__showcase = {}

    def register(self, name: str, proto: Product):
        self.__showcase[name] = proto

    def create(self, protoname: str) -> Product:
        p = self.__showcase.get(protoname)
        return p.create_clone()

具体的にインスタンスを複製するクラスです。
登録したら、そのインスタンスの複製を作成できるようになります。

MassageBox (Productを継承)

# coding: utf-8
import copy

from prototype.framework.product import Product


class MessageBox(Product):

    def __init__(self, decochar: str):
        if len(decochar) > 1:
            raise TypeError('文字は一文字')
        self.__decochar = decochar

    def use(self, s: str):
        length = len(s.encode('utf-8'))
        for i in range(length + 4):
            print(self.__decochar, end='')
        print('')
        print('%s %s %s' % (self.__decochar, s , self.__decochar))
        for i in range(length + 4):
            print(self.__decochar, end='')
        print('')

    def create_clone(self) -> Product:
        return copy.copy(self)

複製するためのインスタンスです。具体的には

***************
* warning box *
***************

のような形で文字列を4方から取り囲む形に出力されます。

UnderlinePen (Productを継承)

# coding: utf-8
import copy

from prototype.framework.product import Product


class UnderlinePen(Product):

    def __init__(self, ulchar):
        if len(ulchar) > 1:
            raise TypeError('文字は一文字')
        self.__ulchar = ulchar

    def use(self, s: str):
        length = len(s.encode('utf-8'))
        print('%s %s %s' % (self.__ulchar, s, self.__ulchar))
        print(' ', end='')
        for i in range(length):
            print(self.__ulchar, end='')
        print('')

    def create_clone(self) -> Product:
        return copy.copy(self)

複製するためのインスタンス2です。具体的には

~ Hello World ~
 ~~~~~~~~~~~

のように下線のような形で文字列を装飾します。

実行ファイル

# coding: utf-8
from prototype.framework.manager import Manager
from prototype.message_box import MessageBox
from prototype.underline_pen import UnderlinePen

if __name__ == '__main__':
    manager = Manager()
    upen = UnderlinePen('~')
    mbox = MessageBox('*')
    sbox = MessageBox('/')
    manager.register('strong message', upen)
    manager.register('warning box', mbox)
    manager.register('slash box', sbox)

    p1 = manager.create('strong message')
    p1.use('Hello World')
    p2 = manager.create('warning box')
    p2.use('warning box')
    p3 = manager.create('slash box')
    p3.use('Hello World')

実行結果

~ Hello World ~
 ~~~~~~~~~~~
***************
* warning box *
***************
///////////////
/ Hello World /
///////////////

実際どう便利か

このデザインパターンは使い所が非常に難しいです。
インスタンスを鬼のように作成して、同じ動作でインスタンスを沢山作成する場合は非常に使えるんではないでしょうか。
また、インスタンスを複製したいけど、マニュアル動作などから生成してるので複製が難しいといった場合も有用ですね。