週末!プログラミング部

ソフトウェア開発ネタを中心に自分でいろいろ調べた内容を自分の勝手な解釈で思うがままに書いくためのブログ。サンプルソースコード、API、プラットフォーム、プログラミング言語、開発環境などを調査、分析して追求いく予定です。

Pythonでデザインパターン 陸ノ型「Prototype」

今回はPythonの勉強しながらデザインパターンのPrototypeをやってみました。
他のパターンにつきましては、以下の目次記事をご参照ください。

Prototypeとは

直訳すると「原型」や「模範」という意味になります。
wikiには「生成されるオブジェクトの種別がプロトタイプ(典型)的なインスタンスであるときに使用され、このプロトタイプを複製して新しいオブジェクトを生成する」とあります。
Prototypeパターンとは「オブジェクトからオブジェクト(複製)を作成するパターン」です。
javaC#とかでいうとclone()のことですね(・`ω´・)b

試行錯誤の時間

Pythonにおけるclone()は標準ライブラリである「copy」を使うことで簡単に実現することができます。

ここで「copy」には浅いコピーであるcopy()と深いコピーであるdeepcopy()があるようです。

copy.copy(x)
x の浅い (shallow) コピーを返します。

copy.deepcopy(x[, memo])
x の深い (deep) コピーを返します。

・・・な ん だ そ れ は ?(゚Д゚≡゚Д゚)?

説明には以下のように書かれています。

浅いコピー (shallow copy) は新たな複合オブジェクトを作成し、
その後 (可能な限り) 元のオブジェクト中に見つかったオブジェクトに
対する 参照 を挿入します。

深いコピー (deep copy) は新たな複合オブジェクトを作成し、
その後元のオブジェクト中に見つかったオブジェクトの
コピー を挿入します。

これによるとオブジェクトの中に別のオブジェクトが含まれていた場合は、

  • 浅いコピーだと中にあるオブジェクトは「参照型」(Cとかでいうポインタ型)になる
  • 深いコピーだと中にあるオブジェクトは「値型」になる

・・・ということかな?(;^ν^)
説明だとよくわからないのでいつもどおりコードを書いて試してみます\(^o^)/

まず、以下のようなクラスを用意しました。
この例ではOuterというアウタークラスの中にInnerというインナークラスがあります。
アウタークラスの中でインナークラスのインスタンスを作成して、get/setを介してアウタークラスからインナークラスにアクセスできます。

class Outer(object) :
    def __init__(self) :
        self.__value = 0
        self.__inner = self.Inner()

    def getOuterValue(self):
        return self.__value

    def setOuterValue(self, value):
        self.__value = value

    def getInnerValue(self):
        return self.__inner.getValue()

    def setInnerValue(self, value):
        self.__inner.setValue(value)

    class Inner(object) :
        def __init__(self) :
            pass

        def getValue(self):
            return self.__value

        def setValue(self, value):
            self.__value = value

このクラスを使ったテストコードは以下のとおりです。

if __name__ == "__main__" :
    
    origin = Outer()
    origin.setOuterValue(1)
    origin.setInnerValue(2)
    
    clone = copy.copy(origin)       # 浅いコピー
    #clone = copy.deepcopy(origin)   # 深いコピー

    print("現在のオブジェクト値")
    print("origin - Outer:", origin.getOuterValue())
    print("origin - Inner:", origin.getInnerValue())
    print("clone  - Outer:", clone.getOuterValue())
    print("clone  - Inner:", clone.getInnerValue())
    
    print("クローンで書き換え後のオブジェクト値")
    clone.setOuterValue(3)
    clone.setInnerValue(4)
    print("origin - Outer:", origin.getOuterValue())
    print("origin - Inner:", origin.getInnerValue())
    print("clone  - Outer:", clone.getOuterValue())
    print("clone  - Inner:", clone.getInnerValue())

まずは浅いコピーcopy.copy(origin)の実行結果です。
クローンで書き換え後のオブジェクト値を見ると、「origin - Inner」の値が「clone - Inner」と同じになっています。 「origin - Outer」は書き換え前のままです。

現在のオブジェクト値
origin - Outer: 1
origin - Inner: 2
clone  - Outer: 1
clone  - Inner: 2
クローンで書き換え後のオブジェクト値
origin - Outer: 1
origin - Inner: 4
clone  - Outer: 3
clone  - Inner: 4

続いて深いコピーcopy.deepcopy(origin)の実行結果です。
これによると「clone - Inner」は書き換わっていますが、「origin - Inner」は書き換わっていません。

現在のオブジェクト値
origin - Outer: 1
origin - Inner: 2
clone  - Outer: 1
clone  - Inner: 2
クローンで書き換え後のオブジェクト値
origin - Outer: 1
origin - Inner: 2
clone  - Outer: 3
clone  - Inner: 4

これらの結果から浅いコピーだとやはり「参照型」となっているようです。
そのため、cloneを使ってInnerの値を書きかえると、originのInnerも書き換わってしまいます。
したがって、Prototypeパターンを実現するには中のオブジェクトすべてをコピーすべきなので、深いコピー (deep copy)を採用すべきでしょうかね?

サンプルコード

いつものごとくサンプルコードです!

# coding: utf-8

import copy

from abc import ABC, ABCMeta, abstractmethod

class Cloneable(metaclass=ABCMeta) :
    
    @abstractmethod
    def createClone(self) :
        pass

class Prototype(Cloneable) :

    def __init__(self, name: str) :
        self.__name = name

    def createClone(self) :
        return copy.deepcopy(self)

    def getName(self) :
        return self.__name

if __name__ == "__main__" :
    origin = Prototype("AAA")
    clone = origin.createClone()

    print("origin:", origin.getName())
    print("clone :",  clone.getName())

以下、実行結果。
originに付けた名前がcloneにも引き継がれていますね(・`ω´・)

python Prototype.py
origin: AAA
clone : AAA