Prototypeパターン

結城浩さんの「Java言語で学ぶデザインパターン入門」を題材にpythonデザインパターンを書いてみる。
今回の題材はPrototypeパターン。クラスからインスタンスをつくるのではなく、インスタンスをコピーすることで、
インスタンスから別のインスタンスをつくるというもの。
pythonでは copyモジュールの deepcopy関数を使うことでインスタンスのコピーをすることができるようなので、
まずは copyモジュールについて学習。copyモジュールは浅いコピーと深いコピー操作を提供してくれるらしい。
浅い (shallow) コピーと深い (deep) コピーの違いが関係してくるのは、複合オブジェクト (リストやクラスインスタンス
ような他のオブジェクトを含むオブジェクト) だけということらしい。
浅いコピー (shallow copy) は新たな複合オブジェクトを作成し、その後 (可能な限り) 元のオブジェクト中に見つかったオブジェクトに対する 参照 を挿入するらしい。
深いコピー (deep copy) は新たな複合オブジェクトを作成し、その後元のオブジェクト中に見つかったオブジェクトの コピー を挿入するらしい。
とりあえず実験。まずは単純にコピー。

>>> import copy
>>> ori = "Hello!"
>>> c1 = copy.copy(ori)
>>> c2 = copy.deepcopy(ori)
>>> c1
'Hello!'
>>> c2
'Hello!'

次にリストのコピー。

>>> lis = [1,2,3]
>>> lis1 = copy.copy(lis)
>>> lis2 = copy.deepcopy(lis)
>>> lis
[1, 2, 3]
>>> lis1
[1, 2, 3]
>>> lis2
[1, 2, 3]
>>> lis[0] = "Hello!"
>>> lis
['Hello!', 2, 3]
>>> lis1
[1, 2, 3]
>>> lis2
[1, 2, 3]

次はリストのリストのコピー。

>>> lis3 = [[1,2,3]]
>>> lis4 = copy.copy(lis3)
>>> lis5 = copy.deepcopy(lis3)
>>> lis3
[[1, 2, 3]]
>>> lis4
[[1, 2, 3]]
>>> lis5
[[1, 2, 3]]
>>> lis3[0][0] = "Hello!"
>>> lis3
[['Hello!', 2, 3]]
>>> lis4
[['Hello!', 2, 3]]
>>> lis5
[[1, 2, 3]]

lis3に対する参照を行った後では浅いコピー(lis4)と深いコピー(lis5)の内容に違いが出た。
これが浅いコピーでは元のオブジェクト中に見つかったオブジェクトに対する 参照 を挿入して、
深いコピーでは元のオブジェクト中に見つかったオブジェクトの コピー を挿入するってことなのか。
要は深いコピーではオブジェクトの内部を辿って複製してくれるというわけか。
浅いコピーで複製されたオブジェクトは、可能な限り参照として渡されちゃうのでリストや辞書などの場合
その要素に参照があればそれは参照として渡されるってことでいいのかな。なのでクラスインスタンスのコピーでも
インスタンスの実体を複製するには深いコピーを行わなきゃならないってことかな。
ではデザインパターンの実装へ。サンプルプログラムは、文字列を枠線で囲って表示したり、下線を付けて表示したり
するもの(prototype.py)。
はじめにProductクラス。

import copy

class Product(object):
    def use(self, s):
        pass
    def createClone(self):
        pass

ここは元々インターフェースなのでメソッドの宣言のみ。
次にManageクラス。

class Manager(object):
    __showcase = dict()
    def register(self, name, proto):
        self.__showcase[name] = proto
    def create(self, protoname):
        p = self.__showcase.get(protoname)
        return p.createClone()

ここではregisterメソッドで製品の名前とProductクラスのインスタンスの一組を辞書に登録。
createメソッドはcreateCloneメソッドを使ってインスタンスを複製する。
次にMessageBoxクラス。

class MessageBox(Product):
    def __init__(self, decochar):
        self.decochar = decochar
	
    def use(self, s):
        length = len(s)
	deco = self.decochar * (length + 4 )
	print deco
	print self.decochar,s,self.decochar
	print deco
	
    def createClone(self):
        p = copy.deepcopy(self)
	return p

ここはProductクラスの実装を行っている。useメソッドは与えられた文字列をdecocharで囲むというもの。
createCloneメソッドはdeepcopy関数を使って自分自身のインスタンスを複製する。
次にUnderlinePenクラス。

class UnderlinePen(Product):
    def __init__(self, ulchar):
        self.ulchar = ulchar
	
    def use(self, s):
        length = len(s)
	print '"%s"' %s
	print " %s " %(self.ulchar * length)
	
    def createClone(self):
        p = copy.deepcopy(self)
	return p

ここもProductクラスの実装。ulcharで与えられる下線を付ける。
最後に動作テスト用のクラス。

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("Hello, world.")
    p3 = manager.create("slash box")
    p3.use("Hello, world.")

最初にManagerクラスのインスタンスを作る。そのインスタンスに対してUnderlinePenクラスのインスタンス
MessageBoxクラスのインスタンスを登録しておく。
実行結果は、

C:\works\python\book1>python prototype.py
"Hello, world."
 ~~~~~~~~~~~~~
*****************
* Hello, world. *
*****************
/////////////////
/ Hello, world. /
/////////////////