「HeadFirstデザインパターン」と「Rubyによるデザインパターン」を読んで Proxy パターン


何番煎じか判りませんがお勉強メモを残します

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
  • 多段プロキシも可能という事
  • 権限があるか? => 防御プロキシが知っている
  • サブジェクトはこのローカルに存在するか? => リモートプロキシが知っている
  • サブジェクトは生成済みか? => 仮想プロキシが知っている
  • プロキシは自分とは違うオブジェクトの振りをし、本物のオブジェクトへの参照を内部に持つ