Pythonで、デザインパターン「Proxy」を学ぶ

GoFのデザインパターンを学習する素材として、書籍「[増補改訂版Java言語で学ぶデザインパターン入門](https://www.hyuki.com/dp/)」が参考になるみたいですね。ただ、取り上げられている実例は、JAVAベースのため、自分の理解を深めるためにも、Pythonで同等のプラクティスに挑んでみました。

# ■ Proxy（プロキシ・パターン）
Proxyパターンは、プログラミングにおけるデザインパターンの一種。Proxy（プロキシ、代理人）とは、大まかに言えば、別の物のインタフェースとして機能するクラスである。その「別の物」とは何でもよく、ネットワーク接続だったり、メモリ上の大きなオブジェクトだったり、複製がコスト高あるいは不可能な何らかのリソースなどである。
Proxyパターンのよく知られている例として、参照カウント付きポインタオブジェクトがある。
複雑なオブジェクトの複数のコピーが必須となる状況では、Proxyパターンに Flyweightパターンを加えることでメモリ使用量を抑えることができる。通常、複雑なオブジェクトのインスタンスは1つだけ生成し、プロキシオブジェクトを複数生成する。それらプロキシオブジェクトは唯一の複雑なオブジェクトへの参照を含む。プロキシへの操作は、オリジナルのオブジェクトにフォワードされる。プロキシオブジェクトが全て破棄されると、参照されていた複雑なオブジェクトの使用していたメモリも解放される。

## UML class and sequence diagram
![W3sDesign_Proxy_Design_Pattern_UML.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/9630ac29-4ebd-c8b9-3565-422acec23fc6.jpeg)
## UML class diagram
![proxy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/bd934685-7a67-4023-c184-c6775a96178e.png)
[(以上、ウィキペディア（Wikipedia）より引用)](https://ja.wikipedia.org/wiki/Proxy_パターン)

# ■ "Proxy"のサンプルプログラム
実際に、Proxyパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。

- まず、プリンタの代理人として、"Alice"を受け付ける
- プリンタ本体が使用できるよう準備する（所要時間は、約10秒）
- 実際に、プリンタを使って、以下のプリントアウト処理"**Nice to meet you**"を実施する。
- つぎに、プリンタの代理人として、"Bob"を受け付ける
- 実際に、プリンタを使って、以下のプリントアウト処理"**Hello, World**"を実施する。

<プリントアウト処理>
 (1) 現在のプリンタの代理人を、プリンタ使用者として扱う
 (2) 前後に"==="で囲って、プリンタ使用者の名前を表示する
 (3) 文字列を表示する。

```console
$ python Main.py 
Printer代理人の名前は現在(Alice)です
Printerのインスタンス(Alice)を作成中..........完了
=== Printer使用者(Alice) ===
Nice to meet you

Printer代理人の名前は現在(Bob)です
=== Printer使用者(Bob) ===
Hello, World

```
ここでの確認ポイントは、<font color="blue">Printerのインスタンス(xxx)を作成中..........完了"</font>の箇所が、一度しか表示されていない点です。
実際のサンプルプログラムでは、複雑なオブジェクトのインスタンスを一度だけ生成できるよう、[Singletonパターン](https://qiita.com/ttsubo/items/c4af71ceba15b5b213f8)を活用しています。



# ■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/Proxy

- ディレクトリ構成

```console
.
├── Main.py
└── proxy
    ├── __init__.py
    └── printer_proxy.py
```
## (1) Subject（主体）の役
`Proxy`役と`RealSubject`役を同一視するためのインタフェースを定めます。
`Subject`役があるおかげで、`Client`役は、`Proxy`役と`RealProxy`役の違いを意識する必要がありません。
サンプルプログラムでは、`Printable`クラスが、この役を努めます。

```python:proxy/printer_proxy.py
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`クラスが、この役を努めます。

```python:proxy/printer_proxy.py
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`クラスが、この役を努めます。

```python:proxy/printer_proxy.py
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`メソッドが、この役を努めます。

```python:Main.py
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()
```

# ■ 参考URL
- [「Java言語で学ぶデザインパターン入門」を終えて(ない)](https://medium.com/since-i-want-to-start-blog-that-looks-like-men-do/java言語で学ぶデザインパターン入門-を終えて-ない-2cc9b34a30b2)
- [Qiita記事「デザインパターン（Design Pattern）#Proxy](https://qiita.com/nirperm/items/cb200058edb935adb26c)