Update 2023.11.18 2017.05.05

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

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

Proxy パターンの使われる場面を説明するのはかなり難しいです。

サンプルを見れば判るのだが,抽象クラスが1つと具象クラスが2つある。これは Decorator パターンと酷似している。違うのは,Decorator パターンはクラスを明示的に呼び分けるが,Proxy パターンは明示的に呼び分けることはしない。

Decorator パターンではクラスを使う人がクラスの内容をよく知った上で使うが,Proxy パターンではクラスの内容を知らないで使う。つまり,使う人にはメリットが良く判らないわけです。

例えば,「間に入ったプロキシサーバがキャッシュを返すためネットにつながっていて実体を見ていると錯覚する」とか「実体を処理をするメインなオブジェクトと呼び出し元の間にこっそり入って知らんぷりしていて盗聴もするオブジェクト」とか,このようなことを hook フック(横取り)と言うことがあります。

GoFのC++のサンプルでは,文書エディタの中でグラフィックオブジェクトの表示に時間がかかるのでとりあえずダミーを見せておき,本当に使うときになったら実体を見せるというものでした。これも hook と言えるでしょう。

クラスを作る人が何かしらこっそり hook したいものがあるときProxy パターンを仕組む!?と言ってよいでしょう。


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


◆◆Proxy パターンとは◆◆

GoFによれば,Proxy パターンの目的は, 「あるオブジェクトへのアクセスを制御するために,そのオブジェクトの代理,または入れ物を提供する。」

GoFによれば,オブジェクトへの単なるポインタよりも多機能で精巧な参照が必要なときには,いつでも Proxy パターンが適用可能である。ここでは,Proxy パターンが適用可能な一般的な状況をいくつかあげる。
1.remote proxy は,別のアドレス空間にあるオブジェクトのローカルな代理を提供する。NEXTSTEP[Add94]はこの目的のために NXProxy クラスを用いている。Coplien[Cop92]は,この種の proxy を“Ambassador(大使)”と呼んでいる。
2.virtual proxy は,コストの高いオブジェクトを要求があり次第生成する。「動機」の節で説明した ImageProxy オブジェクトは,virtual proxy の一例である。
3.protection proxy は,実オブジェクトへのアクセスを制御する。オブジェクトごとに異なるアクセス権が必要なときには,この protection proxy は有用である。たとえば,Choices オペレーティングシステム[CIRM93]の KernelProxy オブジェクトは,オペレーティングシステムのオブジェクトへのアクセスを保護する。
4.smart reference は,通常のポインタに代わるものである。オブジェクトがアクセスされるときに,その smart reference はさらにアクションを実行する。この proxy の典型的な使い方を以下にあげる。
・参照されなくなった実オブジェクトを自動的に解放できるように,実オブジェクトへの参照個数を数えておく(smart pointer とも呼ばれる[Ede92])。
・永続オブジェクトが初めて参照されたときに,それをメモリ上にロードする。
・他のオブジェクトが実オブジェクトを変更することのないように,実オブジェクトがアクセスされる前に,それがロックされていることを確認する。

GoFによれば,Proxy パターンの関連するパターンは次のようなものである。
Adapter パターン:adapter は,オブジェクトに異なるインタフェースを提供する。それとは対照的に,proxy は RealSubject オブジェクトと同じインタフェースを提供する。しかし,アクセス保護のために使われる proxy は,RealSubject オブジェクトならば実行するであろうオペレーションの実行を拒否するかもしれない。したがって,実際上は,proxy のインタフェースは Subject クラスのインタフェースの一部かもしれない。
Decorator パターン:decorator は proxy と似た形態で実装されるが,両者の目的は異なる。decorator はあるオブジェクトに1つ,または複数の責任を追加する。一方 proxy は,あるオブジェクトへのアクセスを制御する。proxy が decorator のように実装される程度は,proxy の種類により異なる。protection proxy はd ecorator とまったく同じように実装されるかもしれない。一方,remote proxy は RealSubject オブジェクトへの直接参照を持たず,“ホストIDとそのホスト上でのローカルアドレス”などの間接参照だけを持つであろう。virtual proxy は,生成時にはファイル名などの間接参照のみを持つが,最後には直接参照を手に入れて使うようになる。

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

ソースコードは巻末にあり,ソースファイルはこのWebの左上隅にありダウンロードできます。
                                 Printable
                      (setPrinterName, getPrinterName, print)

                           ↑                 ↑
                      Printer                 PrinterProxy
(setPrinterName, getPrinterName, print,   (setPrinterName, getPrinterName, print,
 heavyJob)                                 realize)
class Printable は,Java サンプルでは interface でありテンプレートです。
class Printer は,処理の本物の実体です。
class PrinterProxy は,処理のダミーです。とりあえずやることだけやります。

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

抽象クラス Printable のテンプレートについては説明が不要でしょう。Java サンプルでは interface です。

実体の具象クラス Printer がやる作業は,サンプルですのでごく単純に名前の登録,取得,表示をするだけです。前提として,時間のかかる重い作業があるので,クライアント;メインルーチンから直接呼ばれないことになっています。この重い作業はインスタンス化のコンストラクタで呼ばれます。

hook フックする具象クラス PrinterProxy も,名前の登録,取得,表示をするだけです。

クライアント;メインルーチンから見て行くと class PrinterProxy の動作が判ります。
最初に,class PrinterProxy にインスタンス化して同時に名前の登録ができます。インスタンス化後の名前の登録はメソッド setPrinterName でやります。そしてメソッド getPrinterName で名前を取得して表示もできます。

名前の登録時に,実体の class Printable が既にインスタンス化されていればこちらのメソッド setPrinterName も動作します。このクラスがインスタンス化されるのはメソッド print が呼ばれたときです。これは任意の仕様であって,実体の重い作業をなるべく起動しないようにしただけのことです。

全体の動作はこれがすべてです。

◆◆ロックが簡単にできる synchronized デコレータ(Decorator パターンとは別物です)◆◆

Java サンプルでは,class PrinterProxy の次のメソッドがロックされています。
  public synchronized void setPrinterName(String name) {
  private synchronized void realize() {
1番目のメソッドは,実体クラスがインスタンス化されていることを確認した上でこのクラスのメソッドを呼んでいます。2番目のメソッドは,実体クラスがインスタンス化されていないことを確認した上でこのクラスのインスタンス化しています。

これらのメソッドが動作しているときは絶対に割り込まれないようにロックする必要があります。もし,途中で割り込まれると動作の結果に矛盾を生じます。ただ,このサンプルはマルチスレッドではないので,途中で割り込まれることはないのですが,応用では絶対に必要なことです。

Python では,言語仕様としてのロックがありますが,使い方を単純にするため,synchronizedデコレータを作りました。デコレータの使い方はごく簡単であり,上の2つのメソッドの前に
  @synchronized(mylock)
を宣言するだけです。デコレータは同名のメソッドによりつくります(名称は任意)。典型的な作り方としてメソッドがネスト(入れ子)されメソッド名を戻すことになっています。詳細はここでは省略します。このデコレータは英文のWebサイトで見つけたものです。作者には感謝します。

このデコレータが正常に動作していることを確認するための使用例のソースファイルをこのWebの左上隅に添付しました。ソースコードを巻末に掲示しました。synchronizedデコレータを動作させたときとコメントアウトしたときの出力も掲示したので比較してください。スレッドが順番に処理されているものといないものが確認できます。

◆◆ソースコード◆◆

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

ソースファイルは2つです。
・Proxy.py;Proxy パターンのPythonサンプル
・SynchronizedDecorator.py;Synchronizedデコレータの使用例

【Proxy.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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
import time
from abc import ABCMeta, abstractmethod
from threading import Thread, Lock

def synchronized(lock): #Synchronization decorator
    def wrap(f):
        def newFunction(*args, **kwargs):
            lock.acquire()
            try:
                return f(*args, **kwargs)
            finally:
                lock.release()
        return newFunction
    return wrap

mylock = Lock()

class Printable(metaclass = ABCMeta):

    @abstractmethod
    def setPrinterName(self, name):
        pass
    @abstractmethod
    def getPrinterName(self):
        pass
    @abstractmethod
    def printMe(self, string):
        pass

class Printer(Printable):
    def __init__(self, name):
        self.name = name
        self.heavyJob("Printerのインスタンス({})を生成中".format(self.name))
    def setPrinterName(self, name):
        self.name = name
    def getPrinterName(self):
        return self.name
    def printMe(self, string):
        sys.stdout.write("=== {} ===\n".format(self.name))
        sys.stdout.write(string)
    def heavyJob(self, msg):
        sys.stdout.write(msg)
        for i in range(5):
            try:
                time.sleep(1)
            except InterruptedError:
                pass
            sys.stdout.write(".")
        sys.stdout.write("完了。\n")

class PrinterProxy(Printable, Thread):
    def __init__(self, name):
        Thread.__init__(self)
        self.name = name
        self.real = None
    @synchronized(mylock)
    def setPrinterName(self, name):
        if (self.real is not None):
            self.real.setPrinterName(name)
        self.name = name
    def getPrinterName(self):
        return self.name
    def printMe(self, string):
        self.realize()
        self.real.printMe(string)
    @synchronized(mylock)
    def realize(self):
        if (self.real is None):
            self.real = Printer(self.name)

def main():
    p = PrinterProxy("Alice")
    sys.stdout.write("名前は現在{}です。\n".format(p.getPrinterName()))
    p.setPrinterName("Bob")
    sys.stdout.write("名前は現在{}です。\n".format(p.getPrinterName()))
    p.printMe("Hello, world.")

if __name__ == '__main__':
    main()
"""標準出力
名前は現在Aliceです。
名前は現在Bobです。
Printerのインスタンス(Bob)を生成中.....完了。
=== Bob ===
Hello, world.
"""

【SynchronizedDecorator.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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from threading import Thread, Lock
import time

myLock = Lock()

def synchronized(lock): #Synchronization decorator
    def wrap(f):
        def newFunction(*args, **kwargs):
            lock.acquire()
            try:
                return f(*args, **kwargs)
            finally:
                lock.release()
        return newFunction
    return wrap

class MyThread(Thread):
    def __init__(self, n):
        Thread.__init__(self)
        self.n = n
    @synchronized(myLock)
    def run(self):
        for i in range(3):
            print("Thread {}: Start {}...".format(self.n, i))
            time.sleep(1)
            print("Thread {}: Stop  {}...".format(self.n, i))

if __name__ == '__main__':
    threads = [MyThread(i) for i in range(5)]
    for t in threads:
        t.start()
    for t in threads:
        t.join()
"""標準出力
Thread 0: Start 0...
Thread 0: Stop  0...
Thread 0: Start 1...
Thread 0: Stop  1...
Thread 0: Start 2...
Thread 0: Stop  2...
Thread 1: Start 0...
Thread 1: Stop  0...
Thread 1: Start 1...
Thread 1: Stop  1...
Thread 1: Start 2...
Thread 1: Stop  2...
Thread 2: Start 0...
Thread 2: Stop  0...
Thread 2: Start 1...
Thread 2: Stop  1...
Thread 2: Start 2...
Thread 2: Stop  2...
Thread 3: Start 0...
Thread 3: Stop  0...
Thread 3: Start 1...
Thread 3: Stop  1...
Thread 3: Start 2...
Thread 3: Stop  2...
Thread 4: Start 0...
Thread 4: Stop  0...
Thread 4: Start 1...
Thread 4: Stop  1...
Thread 4: Start 2...
Thread 4: Stop  2...

# @synchronized(myLock)をコメントアウトしたとき
Thread 0: Start 0...
Thread 1: Start 0...
Thread 2: Start 0...
Thread 3: Start 0...
Thread 4: Start 0...
Thread 2: Stop  0...
Thread 2: Start 1...
Thread 4: Stop  0...
Thread 4: Start 1...
Thread 1: Stop  0...
Thread 1: Start 1...
Thread 3: Stop  0...
Thread 3: Start 1...
Thread 0: Stop  0...
Thread 0: Start 1...
Thread 3: Stop  1...
Thread 3: Start 2...
Thread 1: Stop  1...
Thread 1: Start 2...
Thread 0: Stop  1...
Thread 0: Start 2...
Thread 4: Stop  1...
Thread 4: Start 2...
Thread 2: Stop  1...
Thread 2: Start 2...
Thread 0: Stop  2...
Thread 2: Stop  2...
Thread 4: Stop  2...
Thread 3: Stop  2...
Thread 1: Stop  2...
"""

以上

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