GoFのデザインパターン(Design Pattern)のプロキシ(プロキシ)をRubyのサンプルコードで紹介します。
プロキシパターンは1つのオブジェクトに複数の関心ことがある場合にそれを分離するために用います。たとえば、オブジェクトの本質的な目的とは異なる「セキュリティ要件やトランザクション管理など」を切り離して実装できます。
サッカーが専門の「サッカー選手」と、チームとの交渉や契約が専門の「代理人」のような関係です。
😸Proxyの構成要素
プロキシの構成要素は次の2つです。
- 対象オブジェクト(subject):本物のオブジェクト
- 代理サブジェクト(proxy):特定の「関心事」を担当、それ以外を対象サブジェクトに渡す
プロキシオブジェクトは対象オブジェクトと同じインタフェースを持ちます。利用する際は、プロキシオブジェクトを通して対象となるオブジェクトを操作します。
🐮プロキシの3つのタイプ
プロキシには次の3つの種類があります。
今回は、防御プロキシと仮想プロキシについてサンプルソースで説明していきます。
🐝サンプルソース1:防御プロキシ
このサンプルでは銀行の窓口業務(入金/出金)を担当するBankAccountクラスと、ユーザー認証を担当するBankAccountProxyクラスにより「関心ことを分離」するプロキシデザインパターンのモデルを作ります。
まず銀行の入出金の窓口業務を行うBankAccountクラスを作成します。
# 銀行の入出金業務を行う(対象オブジェクト/subject) classBankAccount attr_reader:balance
definitialize(balance) @balance = balance end
# 出金 defdeposit(amount) @balance += amount end
# 入金 defwithdraw(amount) @balance -= amount end end
|
次に銀行の入出金業務とは関心ことの異なるユーザー認証を担当する防御プロキシとしてBankAccountProxyクラスを作ります。このクラスはBankAccountクラスと同じインタフェースを持っており、利用する側はプロキシを介して入出金を行います。
# etcはRubyの標準ライブラリで、/etc に存在するデータベースから情報を得る # この場合は、ログインユーザー名を取得するために使う require"etc"
# ユーザーログインを担当する防御Proxy classBankAccountProxy definitialize(real_object, owner_name) @real_object = real_object @owner_name = owner_name end
defbalance check_access @real_object.balance end
defdeposit(amount) check_access @real_object.deposit(amount) end
defwithdraw(amount) check_access @real_object.withdraw(amount) end
defcheck_access if(Etc.getlogin != @owner_name) raise"Illegal access:#{@owner_name} cannot access account." end end end
|
実際に動かしてみます。
# ログインユーザーの場合 account = BankAccount.new(100) # login_userの部分はこの処理を行うMac/Linuxのログイン中のユーザー名に書き換えて下さい proxy = BankAccountProxy.new(account,"login_user") puts proxy.deposit(50) #=> 150 puts proxy.withdraw(10) #=> 140
# ログインユーザーではない場合 account = BankAccount.new(100) proxy = BankAccountProxy.new(account,"no_login_user") puts proxy.deposit(50) #`check_access': Illegal access: no_login_user cannot access account. (RuntimeError)
|
このようにユーザー認証という「特定の関心事」を代理オブジェクトに分離させることができました。
🚕仮想プロキシ
次に仮想プロキシのサンプルです。今回は、先ほどの入出金を行うBankAccountクラスと、BankAccountのインスタンス生成を遅らせるためのVirtualAccountProxyクラスを作成します。インスタンスの生成を遅らせる理由はここではシステム全体の性能向上と仮定します。
まずさきほどと同じ入出金業務のBankAccountクラスです。
# 銀行の入出金業務を行う(対象オブジェクト/subject) classBankAccount attr_reader:balance
definitialize(balance) puts"BankAccountを生成しました" @balance = balance end
defdeposit(amount) @balance += amount end
defwithdraw(amount) @balance -= amount end end
|
次にシステム全体の性能向上を目的としてBankAccountクラスの生成を遅らせる仮想プロキシとして、VirtualAccountProxyクラスを作成します。
# BankAccountの生成を遅らせる仮想Proxy classVirtualAccountProxy definitialize(starting_balance) puts"VirtualAccountPoxyを生成しました。BankAccountはまだ生成していません。" @starting_balance = starting_balance end
defbalance subject.balance end
defdeposit(amount) subject.deposit(amount) end
defwithdraw(amount) subject.withdraw(amount) end
defannounce "Virtual Account Proxyが担当するアナウンスです" end
defsubject @subject|| (@subject = BankAccount.new(@starting_balance)) end end
|
コーディング以上となります。ではこのサンプルを動かしてみます。
proxy = VirtualAccountProxy.new(100) #=> VirtualAccountPoxyを生成しました。BankAccountはまだ生成していません。
puts proxy.announce #=> Virtual Account Proxyが担当するアナウンスです
puts proxy.deposit(50) #=> BankAccountを生成しました #=> 150
puts proxy.withdraw(10) #=> 140
|
結果のようにdepositメソッドを実行するまで、BankAccountクラスが生成されないようになりました。この例では、VirtualAccountProxyが「BankAccountインスタンスの生成タイミング」という関心ことを分離しています。
🏀Tips:method_missingによる委譲
ここでRubyの標準機能のひとつ、method_missingを使ったプロキシを紹介します。
Rubyでは未定義のメソッドが呼び出された場合にmethod_missingが呼び出されます。これを利用することでさきほどの防御プロキシのBankAccountProxyクラスを次のように短くできます。
# ユーザーログインを担当する防御Proxy classBankAccountProxy definitialize(real_object, owner_name) @real_object = real_object @owner_name = owner_name end
# Rubyでは未定義のメソッド呼び出しが発生 => method_missingが呼び出される # method_missingを用いることでdeposit, withdrawを省略 defmethod_missing(name, *args) check_access @real_object.send(name, *args) end
defcheck_access if(Etc.getlogin != @owner_name) raise"Illegal access:#{@owner_name} cannot access account." end end end
|
BankAccountProxyクラスに存在しない#depositメソッド、#withdrawが呼び出された場合、#method_missingが呼ばれます。そして、#method_missing内で@real_objectに格納したオブジェクトの同名のメソッドが呼び出されます。
この方法はForwardableと同じ「委譲」のやり方のひとつです。
ただし、method_missingの利用には次のようなデメリットもあります。このデメリットをしっかり理解したうえで、利用することが大切です。
- ソースコードが追いづらくなる
- マシンパワーを消費する
😀サンプルソース
😎参考リンク
🖥 VULTRおすすめ
「VULTR」はVPSサーバのサービスです。日本にリージョンがあり、最安は512MBで2.5ドル/月($0.004/時間)で借りることができます。4GBメモリでも月20ドルです。 最近はVULTRのヘビーユーザーになので、「ここ」から会員登録してもらえるとサービス開発が捗ります!