Proxyパターン (1) - アクセス制御Proxy

Rubyによるデザインパターン』(ラス・オルセン著 ピアソン・エデュケーション刊)の例をPythonに変換して書いています。目次




  • オブジェクトへのアクセス制限

  • オブジェクトの実際の場所に依存しないアクセス

  • オブジェクトの生成の遅延


という、3つの一見関係が無い問題の、共通の解決策がProxyパターンです。







たとえば、BankAccount(銀行口座)クラスを作っとします。


これを使えば、銀行業務の全てがユーザー端末上から行えますが、
今更

「特定のユーザーしか口座にアクセスできないようにする」

「実際には、BankAccountオブジェクトは(端末ではなく)サーバー上になければならない」

「パフォーマンスの問題上、オブジェクトの生成を出来るだけ遅らせたい」

という要望を実現しなければならない事になりました。



まずは、アクセス制御も何もしないProxyを作ります。

class BankAccount(object):
    """「銀行口座」クラス"""
    def __init__(self, starting_balance=0):
        self._balance = starting_balance
    
    def deposit(self, amount):
        self._balance += amount
    
    def withdraw(self, amount):
        self._balance -= amount
    
    def balance(self):
        return self._balance

class BankAccountProxy(object):
    """「銀行口座」クラス"""
    def __init__(self, real_object):
        self.real_object = real_object
    
    def deposit(self, *a, **kw):
        self.real_object.deposit(*a, **kw)
    
    def withdraw(self, *a, **kw):
        self.real_object.withdraw(*a, **kw)
    
    def balance(self, *a, **kw):
        self.real_object.balance(*a, **kw)

if __name__ == "__main__":
    account = BankAccount(100)
    account.deposit(50)
    account.withdraw(10)
    
    proxy = BankAccountProxy(account)
    proxy.deposit(50)
    proxy.withdraw(10)


accountを直接扱うのと、全く同様に操作できます。



さて、アクセス制御付きのProxyを作ってみましょう。

class IllegalAccessError(StandardError):
    """アクセス権限が無いのにアクセスしたときの例外"""

class BankAccountProtectionProxy(object):
    """「銀行口座」クラスのアクセス制御付きProxy"""
    
    def __init__(self, real_object, owner_name):
        self.real_object = real_object
        self.owner_name  = owner_name
    
    def check_access(self):
        """ユーザーにアクセス権が無い時、例外を送出する"""
        if self.owner_name != "doloop":
            raise IllegalAccessError("%r cannot access amount."%(self.owner_name,))
    
    def deposit(self, *a, **kw):
        self.check_access()
        return self.real_object.deposit(*a, **kw)
    
    def withdraw(self, *a, **kw):
        self.check_access()
        return self.real_object.withdraw(*a, **kw)
    
    def balance(self, *a, **kw):
        self.check_access()
        return self.real_object.balance(*a, **kw)

if __name__ == "__main__":
    account = BankAccount(100)
    
    proxy = BankAccountProtectionProxy(account, "doloop")
    proxy.deposit(50)
    proxy.withdraw(10)
    
    proxy2 = BankAccountProtectionProxy(account, "hogera")
    proxy2.deposit(50) #=> IllegalAccessError: 'hogera' cannot access amount.
    proxy2.withdraw(10)


メソッド呼び出しを、

BankAccountProxyでは、ただ単にself.real_objectに丸投げしていましたが、


BankAccountProtectionProxyでは、self.check_access()を呼び出してから、丸投げしています。




「口座管理」と「アクセス制御」の機能を、

それぞれBankAccountとBankAccountProtectionProxyに分離する事で、

一つのオブジェクトで同時に扱うのに比べて、見通しが良くなります。



つづく