GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。ただ、取り上げられている実例は、JAVAベースのため、自分の理解を深めるためにも、Pythonで同等のプラクティスに挑んでみました。
■ Prototype(プロトタイプ・パターン)
Prototypeパターン(プロトタイプ・パターン)とは、ソフトウェア開発で用いられる、生成に関するデザインパターンの1つである。生成されるオブジェクトの種別がプロトタイプ(典型)的なインスタンスであるときに使用され、このプロトタイプを複製して新しいオブジェクトを生成する。
このパターンは、以下の目的で用いられる。
- Abstract Factory パターンでなされるように、クライアント・アプリケーションにおいてオブジェクトの生成者をサブクラスにすることを回避する
- 標準的な方法(例えば'new')で新しいオブジェクトを作ることによる固有のコストが所与のアプリケーションにとって高すぎる時にそれを回避する
このパターンを実装するには、純粋仮想 (pure virtual method) の clone()メソッドを指定する抽象的(abstract)な基底クラスを宣言する。「多態性を持つコンストラクタ」の能力を必要とする全てのクラスは抽象的な基底クラスから自身を派生させ、clone()の操作を実装する。
UML class and sequence diagram
UML class diagram
□ 備忘録
Prototype
パターンは、クラスからインスタンスをつくるのではなく、インスタンスをコピーすることで、インスタンスから別のインスタンスをつくるというものです。
Prototype
パターンを活用するメリットについては、書籍「増補改訂版Java言語で学ぶデザインパターン入門」P.66に以下の記述があります。
(1) 種類が多すぎてクラスにまとめられない場合
(2) クラスからインスタンス作成が難しい場合
(3) フレームワークと生成するインスタンスを分けたい場合
まあ、大量に、インスタンスを生成するようなユースケースがあるならば、インスタンス生成に関わるオーバヘッド処理の削減につながるんですかねぇー
■ "Prototype"のサンプルプログラム
実際に、Prototypeパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。
- 文字列を、下線を引いて表示する
- 文字列を、枠線で囲って表示する
$ python Main.py
"Hello World"
-------------
***************
* Hello World *
***************
///////////////
/ Hello World /
///////////////
■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/Prototype
- ディレクトリ構成
.
├── Main.py
└── framework
├── __init__.py
├── decoprototype
│ ├── __init__.py
│ ├── message_box_prototype.py
│ └── underline_pen_prototype.py
├── manager.py
└── prototype.py
(1) Prototype(原型)の役
Prototype
役は、インスタンスをコピー(複製)して新しいインスタンスを作るためのインタフェースを定めます。
サンプルプログラムでは、Prototype
クラスが、この役を努めます。
from abc import ABCMeta, abstractmethod
class Prototype(metaclass=ABCMeta):
@abstractmethod
def use(self, s):
pass
@abstractmethod
def createClone(self):
pass
(2) ConcretePrototype(具体的な原型)の役
ConcretePrototype
役は、インスタンスをコピーして新しいインスタンスを作るメソッドを実際に実装します。
サンプルプログラムでは、UnderlinePen
クラスやMessageBox
クラスが、この役を努めます。
import copy
from framework.prototype import Prototype
class UnderlinePen(Prototype):
def __init__(self, ulchar):
self.__ulchar = ulchar
def use(self, s):
length = len(s)
line = self.__ulchar * (length + 2)
print("\"{0}\"".format(s))
print("{0}\n".format(line))
def createClone(self):
clone = copy.deepcopy(self)
return clone
import copy
from framework.prototype import Prototype
class MessageBox(Prototype):
def __init__(self, decochar):
self.__decochar = decochar
def use(self, s):
length = len(s)
line = self.__decochar * (length + 4)
print("{0}".format(line))
print("{0} {1} {2}".format(self.__decochar, s, self.__decochar))
print("{0}\n".format(line))
def createClone(self):
clone = copy.deepcopy(self)
return clone
(3) Client(利用者)の役
Client
役は、インスタンスをコピーするメソッドを利用して、新しいインスタンスを作成します。
サンプルプログラムでは、Manager
クラスやstartMain
メソッドが、この役を努めます。
class Manager(object):
def __init__(self):
self.__showcase = {}
def register(self, name, proto):
self.__showcase[name] = proto
def create(self, protoname):
p = self.__showcase[protoname]
return p.createClone()
from framework.manager import Manager
from framework.decoprototype.underline_pen_prototype import UnderlinePen
from framework.decoprototype.message_box_prototype import MessageBox
def startMain(managerObject):
upen = UnderlinePen("-")
mbox = MessageBox("*")
sbox = MessageBox("/")
managerObject.register("strong message", upen)
managerObject.register("warning box", mbox)
managerObject.register("slash box", sbox)
p1 = managerObject.create("strong message")
p2 = managerObject.create("warning box")
p3 = managerObject.create("slash box")
p1.use("Hello World")
p2.use("Hello World")
p3.use("Hello World")
if __name__ == "__main__":
startMain(Manager())