何番煎じか判りませんがお勉強メモを残します
「HeadFirstデザインパターン」第13章
「Rubyによるデザインパターン」第10章
Proxy パターン
「HeadFirstデザインパターン」でのJavaコードは(だいたい)こんな感じ
第13章にて「その他のパターン」として2ページで紹介されている
- 他のオブジェクトをに対するアクセスを制御する為の代理、またはプレースホルダを提供する
Javaでの実装コードは掲載されていない(´・ω・`)
「Rubyによるデザインパターン」でのRubyコードは(だいたい)こんな感じ
- 銀行口座オブジェクトがあります
- 口座残高を増減するだけのものです
class BankAccount
attr_reader :balance
def initialize(starting_balance = 0)
@balance = starting_balance
end
def deposit(amount)
@balance += amount
end
def withdraw(amount)
@balance -= amount
end
end
bank_account = BankAccount.new(50)
bank_account.balance
# => 50
bank_account.deposit(100)
# => 150
bank_account.withdraw(70)
# => 80
- プロキシを作ってみます
- BankAccountProxyはBankAccount(サブジェクト)への参照を持つ
- BankAccountProxyはサブジェクトと同じインターフェースを持ち、メソッド呼び出しをサブジェクトへ移譲しているだけ
- このプロキシには面白いところは特にない
class BankAccountProxy
def initialize(subject)
@subject = subject
end
def balance
@subject.balance
end
def deposit(amount)
@subject.deposit(amount)
end
def withdraw(amount)
@subject.withdraw(amount)
end
end
bank_account = BankAccount.new(50)
proxy = BankAccountProxy.new(bank_account)
proxy.balance
# => 50
proxy.deposit(100)
# => 150
proxy.withdraw(70)
# => 80
- これは何の意味があるのか?
- プロキシが準備されれば、プロキシとサブジェクトの間に処理を差し込む、良い場所になる
防御プロキシ
- 現状は全く役になっていないBankAccountProxyを サブジェクトへのアクセスを制限する防御プロキシへと作り変える
- 関心事の分離
- BankAccountが関心を持つのは口座残高のみ、BankAccountProxyが関心を持つのはユーザ認証のみ、とする事ができる
class BankAccountProxy
def initialize(subject, owner_name)
@subject = subject
@owner_name = owner_name
end
def balance
check_access
@subject.balance
end
def deposit(amount)
check_access
@subject.deposit(amount)
end
def withdraw(amount)
check_access
@subject.withdraw(amount)
end
private
def check_access
if @owner_name != 'mokoaki'
raise 'hogehoge'
end
end
end
bank_account = BankAccount.new(50)
proxy = BankAccountProxy.new(bank_account, 'mokoaki')
proxy.balance
# => 50
proxy.deposit(100)
# => 150
proxy.withdraw(70)
# => 80
リモートプロキシ
※ほぼコード無し
- 例えばネットワークの向こうにBankAccountオブジェクトが存在していて、そのオブジェクトを利用したかった、なんて事は?
- ネットワーク越しに処理を依頼する責をBankAccountProxyに負わせ、クライアントのコードにはBankAccountオブジェクトを操作しているように思わせ、実はネットワーク越しにBankAccountオブジェクトへアクセス、クライアント的には「あれ?ちょっと返答に時間がかかってるかな?」というような時間経過の後に返り値を受け取る
- こういうのをRPC(SOAP, XMLRPC等がある)と言うらしいです
- ネットワーク越しに存在するBankAccountは口座残高の管理のみに集中し、
- クライアントがBankAccountだと思っているBankAccountProxyはネットワーク越しにBankAccountとバイトコードのやり取りに集中するわけです
仮想プロキシ
- 作成コストがかかるオブジェクトの実生成を、本当に必要になった、その時まで遅延させる
- クライアントがメソッドを呼び出すまでは、サブジェクトへの参照さえも持っていない
- クライアントがメソッドを呼び出した時、プロキシは慌ててサブジェクトへの参照を作成する
class VirtualAccountProxy
def initialize(starting_balance = 0)
@starting_balance = starting_balance
end
def balance
subject.balance
end
def deposit(amount)
subject.deposit(amount)
end
def withdraw(amount)
subject.withdraw(amount)
end
private
def subject
@subject ||= BankAccount.new(@starting_balance)
end
end
proxy = VirtualAccountProxy.new(50)
proxy.balance
# => 50
proxy.deposit(100)
# => 150
proxy.withdraw(70)
# => 80
- このコードにはまだ問題がある
- BankAccountオブジェクト生成の責務がVirtualAccountProxyオブジェクトにハードコードされている
- つまり、サブジェクトとプロキシの結合度が高い
- コードブロックでこれを回避してみる
class VirtualAccountProxy
def initialize(&creation_block)
@creation_block = creation_block
end
def balance
subject.balance
end
def deposit(amount)
subject.deposit(amount)
end
def withdraw(amount)
subject.withdraw(amount)
end
private
def subject
@subject ||= @creation_block.call
end
end
proxy = VirtualAccountProxy.new { BankAccount.new(50) }
proxy.balance
# => 50
proxy.deposit(100)
# => 150
proxy.withdraw(70)
# => 80
- さて、BankAccountとVirtualAccountProxyにおいて balance, deposit, withdraw のメソッドが両方に実装されています
- VirtualAccountProxyのメソッド達は、BankAccountへメソッド呼び出しを移譲する、それだけの為に存在しています
- 3つならまだ我慢できるのかもしれませんが、数が増えてくるとただひたすらに辛く、バグの温床ともなります
- この本では method_missing を用いて回避していました
- (私見) 移譲するなら、Fowardableを使う手もあるのかも知れません
class BankAccountProxy
def initialize(&creation_block)
@creation_block = creation_block
end
def method_missing(name, *args)
subject.send(name, *args)
end
private
def subject
@subject ||= @creation_block.call
end
end
bank_account_proxy = BankAccountProxy.new { BankAccount.new(50) }
bank_account_proxy.balance
# => 50
bank_account_proxy.deposit(100)
# => 150
bank_account_proxy.withdraw(70)
# => 80
- 多段プロキシも可能という事
- 権限があるか? => 防御プロキシが知っている
- サブジェクトはこのローカルに存在するか? => リモートプロキシが知っている
- サブジェクトは生成済みか? => 仮想プロキシが知っている
- プロキシは自分とは違うオブジェクトの振りをし、本物のオブジェクトへの参照を内部に持つ