Proxy
Rubyによるデザインパターン生活。続けます。
本日はProxyパターンを取り上げます。
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 12人 クリック: 193回
- この商品を含むブログ (58件) を見る
Proxyパターンでは、呼び出し対象となるオブジェクトに対して、
同じインタフェースを持つ代理オブジェクトを用意します。
対象となるオブジェクトのメソッドをそのまま呼び出すのではなく、
代理オブジェクトを通じて、対象となるオブジェクトのメソッドを操作します。
Proxyパターンを用いることで、いわゆる「関心事の分離」を実現することができます。
関心事の分離とは、クラス(オブジェクト)の持つ本来の責務とは質の異なる要件(例えばセキュリティ要件やトランザクション管理など)を本来のクラスから切り離して実装することです。
#なんだか、ちょっとゴリ押しでまとめた感があるけど。。
この本では、Proxyパターンの活用方法を以下の3つに分類しています。
- 防御Proxy
- リモートProxy
- 仮想Proxy
まずは防御Proxyから見ていきます。
この章のサンプルでは銀行処理クラス(BankAccount)クラスが登場します。
class BankAccount attr_reader :balance def initialize(balance) @balance = balance end def deposit(amount) @balance += amount end def withdraw(amount) @balance -= amount end end
BankAccountクラスは残高照会処理(balance)と、入金処理(deposit)、引落処理(withdrow)を持っています。
言い換えると、BankAccountクラスは、銀行処理の基盤となる振る舞い(メソッド)は実装していますが、
利用権限を確認するためのユーザの認証処理といったセキュリティに関する要件は実装していません。
ここで、BankAccountクラスにユーザ認証処理を付加してみようと思います。
BankAccountクラスをそのまま修正してもよいのですが、その分、BankAccountクラスは複雑になってしまいます。
これはBankAccountクラスの本来の責務である銀行処理に加えて、ユーザ認証といった関心事の異なる要件を実装してしまうために発生する問題です。
そこでProxyパターンを用いると既存のBankAccountクラスに変更を加えずにユーザ認証機能を実現することができます。
BankAccountの代理となるBankAccountProxyクラスを作成します。
require "etc" class BankAccountProxy def initialize(real_object, owner_name) @real_object = real_object @owner_name = owner_name end def balance check_access @real_object.balance end def deposit(amount) check_access @real_object.deposit(amount) end def withdraw(amount) check_access @real_object.withdraw(amount) end def check_access if(Etc.getlogin != @owner_name) raise "Illegal access: #{@owner_name} cannot access account." end end end
BankAccounrProxyクラスは、インスタンス変数にBankAccountオブジェクトを保持します。
また、BankAccountProxyクラスには、depositやwithdrowといったメソッドを定義しています。
depositメソッドやwithdrowメソッドは、
内部でcheck_accessメソッドを呼び出しユーザ認証を行っています。*1
ユーザ認証をパスした場合のみ、BankAccountオブジェクトに処理を委譲することになります。
プログラムの呼び出しもとは次のようになります。
account = BankAccount.new(100) proxy = BankAccountProxy.new(account, "murayama") puts proxy.deposit(50) puts proxy.withdraw(10)
ユーザ認証をパスした場合の実行結果は以下のとおりです。
150 140
一方で、ユーザ認証に失敗した場合はRuntimeErrorが発生します。
以上が、防御Proxyのお話になります。
ユーザ認証を担当するBankAccountProxyクラスを作成することで、
セキュリティ要件を代理クラスに任せることができました。
つづいて、仮想Proxyのお話です。
仕組みはさきほどの防御Proxyとよく似ていますが、その利用目的が異なります。
仮想Proxyでは、複雑なオブジェクトの生成を遅延させることを目的とします。
VirtualAccountProxyは入金処理(deposit)、引落処理(withdrow)、残高照会処理(balance)が呼び出されるまで、
処理の実体となるBankAccountオブジェクトの生成を遅延します。
class VirtualAccountProxy def initialize(starting_balance) @starting_balance = starting_balance end def balance subject.balance end def deposit(amount) subject.deposit(amount) end def withdraw(amount) subject.withdraw(amount) end def subject @subject || (@subject = BankAccount.new(@starting_balance)) end end
このVirtualAccountProxyは、subjectメソッドを呼び出すことでBankAccountオブジェクトを生成します。
subjectメソッドはdepositメソッドやwithdrowメソッドが呼び出されたときに実行します。
言い換えると、depositメソッドや、withdrowメソッドが呼び出されるまで、
処理の本体となるBankAccountオブジェクトは生成されないことになります。
以上がProxyパターンのお話です。
#リモートProxyの話はおいときます。
せっかくなのでクラス図も載せておきます。
と、ここまでが一般的なProxyパターンのお話です。
ここからは、さきほどの防御Proxy(BankAccountProxy)を、
Rubyらしいコードに変換してみます。
少し話が変わりますが、Rubyには未定義のメソッド呼び出しが発生した場合に、
method_missingという名前のメソッドが呼び出されます。*2
このmethod_missingメソッドを利用することで、
さきほどのBankAccountProxyクラスを次のように実装することができます。
require "etc" class BankAccountProxy def initialize(real_object, owner_name) @real_object = real_object @owner_name = owner_name end def method_missing(name, *args) check_access @real_object.send(name, *args) end def check_access if(Etc.getlogin != @owner_name) raise "Illegal access: #{@owner_name} cannot access account." end end end
depositメソッドや、withdrowメソッドを定義する必要がなくなったのでシンプルになりました。
method_missingメソッドの内部でsendメソッドを使用することで、本体となるオブジェクトのメソッドを呼び出しています。
また、method_missingメソッドの第2匹数*argsは不定個の引数を配列に格納します。
以上、Proxyパターンの勉強でした。おしまい。