デザインパターン(Design Pattern)#Proxy


設計を意識したコードが書けるようになる為に、デザインパターン修行しました。
他のDesign Patternもちょくちょく出していきます。

前置き

デザインパターンをどういう時に、何を、どう使うのかを理解することが一先ずの目標。
(Javaというか静的型付言語は初めてで、且つpython歴もそんなに長くないので、Pythonistaぽっくないところがあると思います。ご指摘ございましたらご教授ください。)

今回は、構造に関するパターンProxy。

Proxyとは

代理(プロキシ)を用意してインスタンスの生成やアクセス制限をコントロールを行い、必要になるときまで代理人に仕事をしてもらう。
ただ、代理人は出来る範囲を超えると、本来の人に役目を渡す。

概要

このサンプルプログラムは、「名前つきのプリンタ」です。Mainクラスは、PrinterProxyのクラスインスタンス(代理人)を生成します。そのインスタンスには「Alice」という名前を付け、名前の表示をする。その後「Bob」という名前に変更して、その名前を表示します。

名前の設定や取得では、まだ本物のPrinterクラスのインスタンス(本人)は生成していない。名前の設定と取得という部分はPrinterProxyクラスが代理で行う。最後にmy_printメソッドを呼び出して、実際にプリントを行う段階になって初めて、PrinterProxyクラスはPrinterクラスのインスタンスを生成する。

PrinterProxyクラスとPrinterクラスを同一視するため、Printableというインタフェースが定義されています。ここでは、Printerクラスのインスタンス生成にとても時間がかかるという前提で、サンプルプログラムが作られています。時間がかかるということを表現するために、コンストラクタからheavy_jobというメソッドを呼び出し、「重い処理」として数秒ほど時間を稼いでいます。

全体のクラス図

シーケンス図

printer.py
import sys
import time
from printable import Printable


class Printer(Printable):

    def __init__(self, name):
        self.__name = name
        self.__heavy_job('Printerのインスタンス({0})を生成中'.format(self.__name))

    def set_printer_name(self, name):
        self.__name = name

    def get_printer_name(self):
        return self.__name

    def my_print(self, string):
        print('===' + ' ' + self.__name + ' ' + '===')
        print(string)

    def __heavy_job(self, msg):
        sys.stdout.write(msg)
        for i in range(1, 5):
            try:
                time.sleep(1)
            except InterruptedError:
                pass
            sys.stdout.write('.')
        print('完了。')

Printerクラスは「本人」を表すクラスです。「重い仕事」としてheavy_jobを行ってます。
あとは、名前を設定のset_printer_nameと名前を取得のget_printer_name、文字列表示するmy_printがあります。Proxyパターンの中心はPrinterProxyクラスの方になってます。

printable.py
from abc import abstractmethod


class Printable():

    @abstractmethod
    def set_printer_name(self, name):
        pass

    @abstractmethod
    def get_printer_name(self):
        pass

    @abstractmethod
    def my_printer(self, string):
        pass

PrinteableインタフェースはPrinterProxyクラスとPrinterクラスを同一視するためのものです。

printer_proxy.py
from printable import Printable
from printer import Printer


class PrinterProxy(Printable):

    def __init__(self, name):
        self.__name = name
        self.__real = None

    def set_printer_name(self, name):
        if (self.__real is not None):
            self.__real.set_printer_name(name)
        self.__name = name

    def get_printer_name(self):
        return self.__name

    def my_print(self, string):
        self.__realize()
        self.__real.my_print(string)

    def __realize(self):
        if (self.__real is None):
            self.__real = Printer(self.__name)

PrinterProxyクラスは代理人の役割を果たすものです。Printableインタフェースを実装しています。
nameフィールドは名前を保持するためのもので、realフィールドは「本人」を保持するためのものです。

コンストラクタでは、名前を設定します。set_printer_nameメソッドは新しく名前を設定します。もしもrealがNoneでなければ、本人に対してもその名前を設定します。しかし、realがNoneなら、PrinterProxyのnameフィールドに値を返します。

my_printメソッドは、この代理人の範囲外の処理なので、realizeメソッドを呼び出し、「本人」を生成します。realizeメソッドを実行した後、realフィールドには本人が保持されているので、real.printを呼び出します。ここは「委譲」です。

set_printer_nameやget_printer_nameを何回呼んでも、Printerのインスタンスは生成されません。Printerのインスタンスは生成されません。Printerのインスタンスが生成されるのは、「本人」が必要になったときだけです。(本人が生成されているかどうかは、PrinterProxyの利用者にはまったく分かりませんし、気にする必要がありません。)

realizeメソッドはrealフィールドがNoneなら、Printerのインスタンスを作ります。realフィールドがNoneでないなら、何もしません。

main.py
from printer_proxy import PrinterProxy


def main():
    pp = PrinterProxy('Alice')
    print('名前は現在' + pp.get_printer_name() + 'です。')
    pp.set_printer_name('Bob')
    print('名前は現在' + pp.get_printer_name() + 'です。')
    pp.my_print('Hello, world.')

if __name__ == '__main__':
    main()

実行結果

名前は現在Aliceです。
名前は現在Bobです。
Printerのインスタンス(Bob)を生成中....完了。
=== Bob ===
Hello, world.

まとめ

ProxyパターンはProxy役が代理人となって、できるだけ処理を肩代わりします。サンプルプログラムでは、Proxy役を使うことによって、実際にmy_printするときまで、重い処理(インスタンス生成)を遅らせることができました。

実際の利用シーンなら初期化に時間がかかる機能がたくさん存在するような場合には、実際にその機能を使う段階になって初めて初期化してると思います。よく使われるデザインパターンの一つだと思います。

参考