GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。ただ、取り上げられている実例は、JAVAベースのため、自分の理解を深めるためにも、Pythonで同等のプラクティスに挑んでみました。
■ Proxy(プロキシ・パターン)
Proxyパターンは、プログラミングにおけるデザインパターンの一種。Proxy(プロキシ、代理人)とは、大まかに言えば、別の物のインタフェースとして機能するクラスである。その「別の物」とは何でもよく、ネットワーク接続だったり、メモリ上の大きなオブジェクトだったり、複製がコスト高あるいは不可能な何らかのリソースなどである。
Proxyパターンのよく知られている例として、参照カウント付きポインタオブジェクトがある。
複雑なオブジェクトの複数のコピーが必須となる状況では、Proxyパターンに Flyweightパターンを加えることでメモリ使用量を抑えることができる。通常、複雑なオブジェクトのインスタンスは1つだけ生成し、プロキシオブジェクトを複数生成する。それらプロキシオブジェクトは唯一の複雑なオブジェクトへの参照を含む。プロキシへの操作は、オリジナルのオブジェクトにフォワードされる。プロキシオブジェクトが全て破棄されると、参照されていた複雑なオブジェクトの使用していたメモリも解放される。
UML class and sequence diagram
UML class diagram
■ "Proxy"のサンプルプログラム
実際に、Proxyパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。
- まず、プリンタの代理人として、"Alice"を受け付ける
- プリンタ本体が使用できるよう準備する(所要時間は、約10秒)
- 実際に、プリンタを使って、以下のプリントアウト処理"Nice to meet you"を実施する。
- つぎに、プリンタの代理人として、"Bob"を受け付ける
- 実際に、プリンタを使って、以下のプリントアウト処理"Hello, World"を実施する。
<プリントアウト処理>
(1) 現在のプリンタの代理人を、プリンタ使用者として扱う
(2) 前後に"==="で囲って、プリンタ使用者の名前を表示する
(3) 文字列を表示する。
$ python Main.py
Printer代理人の名前は現在(Alice)です
Printerのインスタンス(Alice)を作成中..........完了
=== Printer使用者(Alice) ===
Nice to meet you
Printer代理人の名前は現在(Bob)です
=== Printer使用者(Bob) ===
Hello, World
ここでの確認ポイントは、Printerのインスタンス(xxx)を作成中..........完了"の箇所が、一度しか表示されていない点です。
実際のサンプルプログラムでは、複雑なオブジェクトのインスタンスを一度だけ生成できるよう、Singletonパターンを活用しています。
■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/Proxy
- ディレクトリ構成
.
├── Main.py
└── proxy
├── __init__.py
└── printer_proxy.py
(1) Subject(主体)の役
Proxy
役とRealSubject
役を同一視するためのインタフェースを定めます。
Subject
役があるおかげで、Client
役は、Proxy
役とRealProxy
役の違いを意識する必要がありません。
サンプルプログラムでは、Printable
クラスが、この役を努めます。
class Printable(metaclass=ABCMeta):
@abstractmethod
def setPrinterName(self, name):
pass
@abstractmethod
def getPrinterName(self):
pass
@abstractmethod
def myPrint(self, string):
pass
(2) Proxy(代理人)の役
Proxy
役はClient
役からの要求をできるだけ処理します。
もしも、自分だけで処理できなかったら、Proxy
役はRealSubject
役に仕事をお任せします。
Proxy
役は、本当にRealSubject
役が必要になってからRealSubject
役を生成します。
Proxy
役は、Subject
役で定められているインタフェースを実装しています。
サンプルプログラムでは、PrinterProxy
クラスが、この役を努めます。
class PrinterProxy(Printable):
def __init__(self, name):
self.__name = name
self.__real = None
def setPrinterName(self, name):
self.__name = name
def getPrinterName(self):
return self.__name
def myPrint(self, string):
self.__real = Printer.getPrinter(self.__name)
self.__real.myPrint(string)
(3) RealSubject(実際の主体)の役
「代理人」のProxy
役では手が負えなくなったときに登場するのが、「本人」のRealSubject
役です。
この役も、Proxy
役と同じくSubject
役で定められているインタフェースを実装しています。
サンプルプログラムでは、Printer
クラスが、この役を努めます。
class Printer(Printable):
@classmethod
def getPrinter(cls, name):
if not hasattr(cls, "_instance"):
cls._instance = cls(name)
else:
cls._instance.__name = name
return cls._instance
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 myPrint(self, string):
print('=== Printer使用者({}) ==='.format(self.__name))
print(string)
print("")
def __heavyJob(self, msg):
print(msg, end='')
for _ in range(10):
time.sleep(1)
print('.', end='')
print('完了')
(4) Client(依頼人)の役
Proxy
役を利用する役です。
サンプルプログラムでは、startMain
メソッドが、この役を努めます。
from proxy.printer_proxy import PrinterProxy
def startMain():
p = PrinterProxy("Alice")
print("Printer代理人の名前は現在({})です".format(p.getPrinterName()))
p.myPrint("Nice to meet you")
p.setPrinterName("Bob")
print("Printer代理人の名前は現在({})です".format(p.getPrinterName()))
p.myPrint("Hello, World")
if __name__ == '__main__':
startMain()