Programming log - Shindo200

イベント参加記録とプログラミング系の雑記

「Forwardableモジュール」と「委譲」について学びました

ある程度の規模のWebサービスを作ろうとすると、Rubyの組み込みライブラリやRailsMVC構造だけで設計することに限界を感じてきてしまいます。そんなときに、様々なデザインパターンや便利なモジュールを知っていれば、最適な設計を選択することができるかもしれません。例えば、過去の記事で紹介したObservableモジュールを使えば、見通しが良いコードが書ける場面があるかもしれません。その他にも、便利なモジュールはたくさんあると思いますので、少しずつ調べてブログに残していきたいと考えています。

今回は、わりと忘れがちなRuby標準添付モジュールである「Forwardable」と、それに関わる「委譲」という仕組みについて調べてみました。

Forwardableモジュール

Forwardableモジュールは、クラスに対してメソッドの委譲機能を追加するモジュールです。「委譲」とは何でしょうか。

Forwardableモジュール
http://docs.ruby-lang.org/ja/2.1.0/class/Forwardable.html

委譲(Delegate

オブジェクト指向プログラミングにおいて、再利用したい機能を自身のオブジェクトに取り込むのではなく、その機能を持つオブジェクトに処理を依頼することを「委譲」と呼びます。処理を依頼するオブジェクトを「委譲元オブジェクト」、依頼を受けて処理を行うオブジェクトを「委譲先オブジェクト」と呼びます。あるオブジェクトに定義されているメソッドやプロパティを他のオブジェクトでも利用したい場合、「委譲」以外に「継承」や「ミックスイン」を使うという方法もあります。「継承」や「ミックスイン」を使うと、継承先(もしくはミックスイン先)のオブジェクトに定義されている全ての機能が利用できようになりますが、「委譲」は一部の機能だけが利用できるようになります。「余計な機能は取り込みたくない」というときに委譲を使うと良いのではないでしょうか。また、委譲元オブジェクトと委譲先オブジェクトに親子関係がうまれないところも委譲のポイントだと思います。

デモンストレーション

デモンストレーションとして、スタック機能を持つクラスを実装してみましょう。まずはForwardableモジュールを使わずに実装してみます。

class Stack < Array; end

はい、出来上がりました。スタックに必要な機能は既にArrayクラスが持っていますので、Arrayクラスを継承したクラスを作るだけで実装は終わりです。スタックとして使えるかどうか試してみましょう。

stack = Stack.new
stack.push 1, 2, 3
puts stack.pop
# => 3

意図した通りに動いていますね。

しかし、この実装には大きな問題があります。それは「#shiftなどの余計なメソッドが使えてしまうこと」です。Arrayクラスを継承してしまったのですから当然ですね。何とかしないといけません。Rubyには定義済みのメソッドをクラスから取り除くことができるが.undefというメソッドがありますので、これを使って#shiftメソッドを取り除いてみましょう。

class Stack < Array
  undef :shift
end

stack = Stack.new
stack.push 1, 2, 3
stack.shift
# => NoMethodError

#shiftメソッドを取り除くことに成功しました。他にも#unshift、#[]などもスタックには不要ですので、これらも取り除きたいですね。
しかし、いちいち.undefを使ってメソッドを取り除かないといけないのは面倒です。もう少し楽にスタックを実装できないのでしょうか。

そこで登場するのがForwardableモジュールです。Forwardableモジュールを使うと、必要なメソッドだけを持つスタックを簡単に実装できます。

require 'forwardable'

class Stack
  extend Forwardable

  def initialize
    @ary = []
  end

  def_delegators :@ary, :push, :pop, :clear, :size
end

Forwardableモジュールの使い方は『Ruby Reference Manual』見てください。

Forwardableモジュール
http://docs.ruby-lang.org/ja/2.1.0/class/Forwardable.html

では、先ほど作ったStackクラスを使ってみます。

stack = Stack.new
stack.push 1, 2, 3
stack.shift
# => NoMethodError

結果を見ると、必要なメソッドだけを持つスタックを実装できていることがわかりますね。

委譲の利用しどころを考えた

デモンストレーションでは「使えるメソッドに制限をかける」という目的で委譲を利用したように見えますが、委譲を利用する本来の目的は「他のオブジェクトに処理を任せること」だそうです。「このクラスではこんな処理を担当するべき」「あのクラスではこんな処理を担当するべき」といった感じに、オブジェクトの責務を考えて設計したいときに委譲を使うと効果的だと思いました。
オブジェクトの責務を考える為に、MVCフレームにおいてControllerクラスからViewクラスにレンダリングの指示を送る処理を委譲を使って書いてみました。

require 'forwardable'

class View
  def render(context)
    # レンダリング処理
  end
end

class BaseController
  extend Forwardable

  def initialize
    @view = View.new
  end

  def_delegator :@view, :render
end

class AppController < BaseController
  def index
    render :index
  end
end

Railsのrenderメソッドを意識して書いてみましたが、いかがでしょうか。

Railsで委譲

Railsで委譲を利用する場合は、ActiveSupportの.delegateメソッドを使う方法もあるようです。
.delegateメソッドはForwardableモジュールの.def_delegatorメソッドと似たような処理を行います。

class Foo < ActiveRecord::Base
  def method1
    'method1'
  end
end

class Bar < ActiveRecord::Base
  belongs_to :foo
  delegate :method1, to: :foo
end

bar = Bar.new
bar.foo = Foo.new
puts bar.method1
# => 'method1'

詳しくはRailsのドキュメントを見てください。

RailsAPIsドキュメントの#delegateメソッド
http://api.rubyonrails.org/classes/Module.html#method-i-delegate

以上、簡単にですが調べたことのまとめでした。