Update 2023.11.18 2017.05.05

Python デザインパターン サンプルコード Prototype
結城 浩「Java言語で学ぶデザインパターン入門」をPython化
Python3(3.11)で動くソースコード(.pyファイル .ipynbファイル)あります
「anaconda3」on .py「PyCharm」.ipynb「Jupyter Notebook」

◆◆Prototype パターンの使われる場面◆◆

Windowsのアクセサリであるペイントを見てみよう。ペンなどの筆記具があり,いろいろな図形パターンがあり,いろいろな色が選べたりします。そして,必ずこの3つを組み合わせて使っています。サンプルも同じような構造になっていることに気づくはずです。

ペイントがそうであるようにプログラミング技術として,クラスをコピーするよりオブジェクトをコピーする方がはるかに有用である場面は多いです。これはアルゴリズムとは関係なく適用できます。(ペイントがPrototype パターンを本当に適用しているかは判らないが,Prototype パターンを世界で初めて適用したのがスケッチパッドだそうです)


(2023-11-18)Python3.11で動作確認済み


◆◆Prototype パターンとは◆◆

GoFによれば,Prototype パターンの目的は, 「生成すべきオブジェクトの種類を原型となるインスタンスを使って明確にし,それをコピーすることで新たなオブジェクトの生成を行う。」

GoFによれば,Prototype パターンは,オブジェクトがどのように生成され,組み合わされ,表現されるのかということからシステムが独立であるべき場合に,適用することができる。さらに,以下のような場合にも適用できる。
・インスタンス化されるクラスが,たとえば,ダイナミックローディングにより,実行時に明らかになる場合。
・生成されるオブジェクトのクラス階層とパラレルな関係になる factory のクラス階層を作ることを避けたい場合。
・クラスのインスタンスが,状態の数少ない組み合わせの中の1つを取る場合。この可能な組み合わせ1つ1つに相当するインスタンスを prototype としてあらかじめ用意しておき,その複製を行う方が,毎回クラスを適当な状態でインスタンス化するよりも便利であろう。

GoFによれば,Prototype パターンの関連するパターンは次のようなものである。
Abstract Factory パターン: Prototype パターンと Abstract Factory パターンはある面で競合している。しかし,これらは一緒に使うこともできる。すなわち,AbstractFactory パターンに prototype の集合を保存しておき,その中からオブジェクトの複製を行ってそれを返すようにすることができる。
Composite パターン,Decorator パターン: これらのパターンを駆使している設計では,Prototype パターンも有効に使える場合が多い。

◆◆Prototype パターンのサンプルのクラスの構成◆◆

ソースコードは巻末にあり,ソースファイルはこのWebの左上隅にありダウンロードできます。
                   Product                ←         Manager
               (use, createClone)               (register, create)
               ↑              ↑
         MessageBox          UnderlinePen
    (use, createClone)    (use, createClone)
class Product は,テンプレートであり抽象クラスです。Java ではインターフェイスです。
class MessageBox は,具象クラスの1つです。
class UnderlinePen は,具象クラスの1つです。
class Manager は,インスタンスを複製します。

サンプルは,文字列を装飾文字で装飾するというごく簡単なものです。装飾パターンは2つあり,「class MessageBox」と「class UnderlinePen」でそれを実装します。実際の応用ではパターンを増やすときはこれらに相当するクラスを増やすことになります。

◆◆Prototype パターンの実装◆◆

クライアント;メインルーチンはたくさんのオブジェクトを呼ぶわけですが,その呼び方を観察するとプログラミング技術が見えてきます。一連の作業の1つが次です。
    upen = UnderlinePen("~")
    manager.register("strong message", upen)
    p1 = manager.create("strong message")
    p1.use("Hello, world.")
最初は,装飾パターンを選ぶクラスを,装飾文字をパラメータとしてインスタンス化しています。次にこのインスタンスを名前を付けて登録しています。そしてこの名前によりオブジェクトをつくり(コピーし),そのオブジェクトを使って装飾される文字列をパラメータにして具体的に装飾するメソッドを呼んでいます。

装飾パターンを決定しているのがクラス MessageBox とクラス UnderlinePen のメソッド use です。この2つは装飾する部分の詳細を除いては同じ構成です。

メソッド createClone は,オブジェクトのコピーを戻り値とします。これには Python の copy.deepcopy が使われています。オブジェクトを deepcopy すると参照関係の詳細までコピーされます。

これらのスーパークラス Product は抽象クラス・抽象メソッドでありテンプレートです。具象グラスはいくらでも増やすことができるわけです。

クラス Manager では,上の一連の作業で呼ばれているメソッド register とメソッド create が実装されています。

メソッド register では,オブジェクトを名前を付けて辞書に登録できます。

メソッド create では,メソッド createClone を使って登録されているオブジェクトを戻すことができます。

このクラス Manager には具象クラスの名前が出てきません。密な関係がありませんのでお互いに独立して修正ができてしまいます。

◆◆ソースコード◆◆

このWebの左上隅からダウンロードできます。

ソースファイルは1つです。
・Prototype.py;Prototype パターンのPythonサンプル

【Prototype.py】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from abc import ABCMeta, abstractmethod
import copy

class Product(metaclass = ABCMeta):

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

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

class MessageBox(Product):
    def __init__(self, decochar):
        self.decochar = decochar
    def use(self, s):
        length = len(s)
        deco = self.decochar * (length + 4 )
        sys.stdout.write("{}\n".format(deco))
        sys.stdout.write("{} {} {}\n".format(self.decochar,s,self.decochar))
        sys.stdout.write("{}\n".format(deco))
    def createClone(self):
        p = copy.deepcopy(self)
        return p

class UnderlinePen(Product):
    def __init__(self, ulchar):
        self.ulchar = ulchar
    def use(self, s):
        length = len(s)
        sys.stdout.write('"{}"\n' .format(s))
        sys.stdout.write(" {}\n".format(self.ulchar * length))
    def createClone(self):
        p = copy.deepcopy(self)
        return p

def 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.")

if __name__== '__main__':
    main()
"""標準出力
"Hello, world."
 ~~~~~~~~~~~~~
*****************
* Hello, world. *
*****************
/////////////////
/ Hello, world. /
/////////////////
"""

以上

トップページに戻る
inserted by FC2 system