銀行員からのRailsエンジニア

銀行員からのRailsエンジニア

銀行員から転身したサービス作りが大好きなRailsエンジニアのブログです。個人で開発したサービスをいくつか運営しており、今も新しいサービスを開発しています。転職して日々感じていること、個人開発サービス運営のことなどを等身大で書いていきます。

【Rubyによるデザインパターンまとめ8】プロキシパターン

コードの品質向上のため、Rubyデザインパターンを解説した名著である Rubyによるデザインパターン で紹介されているデザインパターンを1つずつまとめており、今回が第8弾です。(毎週1つが目標です!)

前回の記事(アダプターパターンのまとめ)はこちらです。
ysk-pro.hatenablog.com

この本で紹介されているサンプルコードそのままではなく、オリジナルのコードで説明しています。

今回は プロキシ(Proxy)パターン についてまとめました。

f:id:ysk_pro:20191229171915p:plain

プロキシパターンとは

本来呼び出したいオブジェクトとの間にオブジェクトを挟むことで、様々な処理を差し込むことができる デザインパターンです。

言い換えると、本来呼び出したいオブジェクトを別のオブジェクト経由で呼び出し、別のオブジェクトに様々な処理を追加することができます。

インターフェースは、本来呼び出したいオブジェクトと同じにします。

よく使用されるケースとしては、アクセス制御生成コストのかかるオブジェクトのインスタンス化遅延 などがあり、これらをプロキシ(挟んだオブジェクト)側で実装することで、関心事を分離することができます。(アクセス制御などが本来呼び出したいオブジェクトの関心事ではない場合に、それをプロキシ側に切り出すことができます)

プロキシ(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によるデザインパターン の中では他の使い方の解説もされているので、ご興味ある方は是非合わせてご覧ください。

Rubyによるデザインパターン

Rubyによるデザインパターン

次回は、デコレータ(Decorator)パターンをまとめます。

来週も頑張ります!

(追記)
デコレータパターンについてまとめました!
是非合わせてご覧ください。
ysk-pro.hatenablog.com