コードの品質向上のため、Rubyでデザインパターンを解説した名著である Rubyによるデザインパターン で紹介されているデザインパターンを1つずつまとめており、今回が第8弾です。(毎週1つが目標です!)
前回の記事(アダプターパターンのまとめ)はこちらです。
ysk-pro.hatenablog.com
この本で紹介されているサンプルコードそのままではなく、オリジナルのコードで説明しています。
今回は プロキシ(Proxy)パターン についてまとめました。
プロキシパターンとは
本来呼び出したいオブジェクトとの間にオブジェクトを挟むことで、様々な処理を差し込むことができる デザインパターンです。
言い換えると、本来呼び出したいオブジェクトを別のオブジェクト経由で呼び出し、別のオブジェクトに様々な処理を追加することができます。
インターフェースは、本来呼び出したいオブジェクトと同じにします。
よく使用されるケースとしては、アクセス制御 や 生成コストのかかるオブジェクトのインスタンス化遅延 などがあり、これらをプロキシ(挟んだオブジェクト)側で実装することで、関心事を分離することができます。(アクセス制御などが本来呼び出したいオブジェクトの関心事ではない場合に、それをプロキシ側に切り出すことができます)
プロキシ(Proxy)は「代理」という意味であり、中継サーバである「プロキシサーバ」と同じイメージです。
文章の説明よりも実際のコードを見た方が分かりやすいと思うので、以下のサンプルコードをご覧ください。
サンプルコード
商品の購入、商品へのコメントができるプログラムを考えてみます。
class Item attr_reader :name def initialize(name) @name = name end def buy puts "商品「#{name}」を購入するよ" end def comment puts "商品「#{name}」にコメントするよ" end end
商品の購入・コメントは、次のように実行できます。
item = Item.new('MacBook Air') item.buy item.comment
実行結果は次のようになります。
商品「MacBook Air」を購入するよ 商品「MacBook Air」にコメントするよ
このコードをプロキシパターンを使って、ログインユーザーのみ 購入・コメントができるようにしてみましょう。(プロキシパターンとは のところで触れた アクセス制御 の例です)
class ItemProxy attr_reader :item, :user_name def initialize(item, user_name = nil) @item = item @user_name = user_name end def buy check_login item.buy end def comment check_login item.comment end private def check_login raise 'ログインしてください' unless login? end def login? user_name == 'ログインユーザー' # 本来はここでログインユーザーかどうか確かめるロジックを書く end end
ItemProxyクラスをItemクラスとの間に挟み、ログインユーザーかどうかを確かめる処理を入れています。
次のように、ログインしていないユーザーで実行するとエラーとなります。
proxy = ItemProxy.new(item)
proxy.buy
proxy.comment
次のように、ログインユーザーで実行するとエラーは発生せず、先ほどと同じ実行結果となります。
proxy2 = ItemProxy.new(item, 'ログインユーザー') proxy2.buy proxy2.comment
こうすることによって、Itemクラスの関心事ではない ログインしているかどうか についてをItemクラスに書かなくてもよくなりました。
さらにここで、method_missingメソッド を使ってリファクタリングしてみましょう。
method_missingメソッドは、呼び出したメソッドが存在しないときに呼び出されるメソッドです。
class ItemProxy attr_reader :item, :user_name def initialize(item, user_name = nil) @item = item @user_name = user_name end def method_missing(name, *args) check_login item.send(name, *args) end private def check_login raise 'ログインしてください' unless login? end def login? user_name == 'ログインユーザー' # 本来はここでログインユーザーかどうか確かめるロジックを書く end end
実行方法・実行結果は先ほどと同じになります。
method_missingメソッドを書くことで、ItemProxyクラスにbuyメソッドとcommentメソッドを記載する必要がなくなりました。
こうすることで、Itemクラスのメソッドを増やした場合に ItemProxyクラスの修正が不要になります。
アダプターパターンとの違い
アダプターパターンは、オブジェクトのインターフェースを変換するためにそのオブジェクトをラップするパターンです。
(アダプターについての記事はこちら)
【Rubyによるデザインパターンまとめ7】アダプターパターン - 銀行員からのRailsエンジニア
どちらも本来呼び出したいオブジェクトを別のオブジェクト経由で呼び出しているところが似ておりますが、以下の点で異なります。
- アダプターパターンは、オブジェクトのインターフェースを変換する
- プロキシパターンは、インターフェースは変えずにアクセス制御などの処理を追加する
おわりに
ここまで読んでいただきありがとうございます。
この記事では、プロキシパターンのよくある使い方の1つである「アクセス制御」の説明しかできませんでしたが、Rubyによるデザインパターン の中では他の使い方の解説もされているので、ご興味ある方は是非合わせてご覧ください。
- 作者:Russ Olsen,ラス・オルセン
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
次回は、デコレータ(Decorator)パターンをまとめます。
来週も頑張ります!
(追記)
デコレータパターンについてまとめました!
是非合わせてご覧ください。
ysk-pro.hatenablog.com