LoginSignup
4
7

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-01-25

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

■ Proxy(プロキシ・パターン)

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

UML class and sequence diagram

W3sDesign_Proxy_Design_Pattern_UML.jpg

UML class diagram

proxy.png
(以上、ウィキペディア(Wikipedia)より引用)

■ "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クラスが、この役を努めます。

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クラスが、この役を努めます。

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クラスが、この役を努めます。

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メソッドが、この役を努めます。

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

4
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
7