Edited at

Rubyの委譲標準ライブラリのまとめ

More than 5 years have passed since last update.

Rubyで委譲を実現するために、大きく二つなライブラリを使っています。


  • forwardable

  • delegate

この二つのライブラリはすごく便利ですが、どう使うかと、どの場面に使えばいいかをかなり忘れがちなので、簡単まとめたいと思います。

まずはforwardableからまとめます。


Forwardable


使い方


1.クラス定義で使う

Ruby magazineによると、Forwardableライブラリは 「明示的に指定したメソッドだけを委譲する」。これはどういう意味でしょう?簡単にいうと、forwardableライブラリは以下の二つのメソッドがポイントです:


  • def_delegator

  • def_delegators

この二つのメソッドはクラスメソッドであり、使いたいクラスに extend Forwardableで拡張するとこの二つのメソッドが使えるようになります。この二つのメソッドは共通点は、委譲元のメソッドを、他のオブジェクトに任せるという点です。例えば

class Bar

def convenient_method; p "xxx"; end
def convenient_method2; p "xxx"; end
def convenient_method3; p "xxx"; end
end

class Foo
extend Forwardable
def initialize; @q ||= Bar.new ; end
def_delegator :@q, :convenient_method, :same_convinent_method
def_delegators :@q, :convenient_method, :convenient_method2, :convenient_method3
end

Foo.new.same_convinent_method #xxx

この例を見ればだいたいの使い方が分かると思います:


  • def_delegatorはおそらく alias見たいな感じの使い方で、三つのパラメータを受け取り、一つ名は「任せたいオブジェクト」、二つ名は「任せたいオブジェクトの使いたいメソッド」、三つ名は「任せたいオブジェクトのメソッドを使って、どの名で呼び出されたい」となります

  • def_delegatorsは配列を受け取り、def_delegatorと違って別名でメソッドを利用するではなく、そのままのメソッド名で任せたいという意味で、任せたいメソッド配列を受け取る。


2.インスタンスオブジェクトで使う

上の使い方は一つのクラスに対して委譲するとなります。直、いまいち使うインスタンスしか任せたい場合は、どうすればよいでしょう?そのときは SingleForwardable を使えばよいです。

printer = String.new

printer.extend SingleForwardable # prepare object for delegation
printer.def_delegator "STDOUT", "puts" # add delegation for STDOUT.puts()
printer.puts "Howdy!"

使い方はインスタンスオブジェクトを直接に SingleForwardableを拡張すればよいです。拡張した後はそのオブジェクトがdef_delegatordef_delegatorsが使え、上のようにどれかのオブジェクトに任せたいメソッドを定義すれば委譲することができます。


使う場面

Forwardableモジュールを、いつ、どの場面で使えばよいかを結構不思議に思う方がいらっしゃるとおもいます。私は個人的に、Forwardableを使って簡潔にオブジェクトの部品化ができると思います。

例えば


「パソコン」というオブジェクト設計したい。

パソコンの機能は、

 - タイピング

 - 画像見える

見たいな機能を搭載した。

インターフェスは以下のように設計したい。

 Computer.new.type

Computer.new.view_screen

だけど、すでにKeyboardとScreenというクラスが存在し、すでにKeyboard#typeScreen#view_screenも存在するので、KeyboardとScreenをComputerの部品として使って、typeとview_screenを委譲して使えばコードが奇麗に見えるでしょう。

class Computer

extend Forwardable
def keyboard; @keyboard ||= Keyboard.new; end
def screen; @screen ||= Screen.new; end

def_delegators :@keyboard, :type
def_delegators :@screen; :view_screen
end

まとめると、私が個人的に、一つのオブジェクトが複数のオブジェクト(機能)から生成されるとき、Forwardableがとても便利です。また、ほとんどのケースは extend Forwardableを使います、SingleForwardableは個人的にほとんど使っていません。


Delegate


使い方

DelegateはForwardableと同じ、委譲として使いますが、違う点としてはForwardableがdef_delegatordef_delegatorsを使って、明示的に、どのメソッドを任せたいを使うが、Delegateはクラスにほぼ全てのメソッドを任せたいという意味です。

Delegateライブラリは主に二つの使い方があります


  • DelegateClass()

  • SimpleDelegator


1. DelegateClass()

DelegateClassの使い方は、クラスを引数として受け取り、そのクラスのオブジェクトにインスタンスメソッドを委譲するという感じです。

例えば

class Foo < DelegateClass(Array)

end

こう定義すると、FooがArrayのメソッドを使えるようになります。

Foo.new.push 25

ここで、それならFooクラスがArrayを継承すればいいじゃない、の鋭い読者がいるでしょう。確かにArrayを継承するのも、「メソッドを使える」という目的を果たせますが、問題はオブジェクト指向の観点から、FooはArrayの子クラスではない、と意味したい場合は、無理矢理Arrayを継承するのがとってもまずいので、その時は DelegateClassの最高さが分かるでしょう。

DelegateClassを使って、委譲先のクラスのインスタンスメソッドを使えるだけではなく、__setobj__というメソッドを使って、メソッドの受け取りオブジェクトを実行時に変えることもできます。setobjについて、下の使う場面でもうちょっと詳しく説明します。


2. SimpleDelegator

SimpleDelegatorの使い方はすごく簡単で、SimpleDelegator.new の引数に渡したオブジェクトのすべてのメソッドが委譲されます。以下の例を見れば使い方が分かると思います。

class Foo; def i_am_awesome; p "x"; end

bar = SimpleDelegator.new(Foo.new)
bar.i_am_awesome #x

この例では、barがFooのインスタンスメソッドをすべて Foo.newで作られたオブジェクトに任せるという意味です。

また、bar.__setobj__ を使って、委譲先のインスタンスオブジェクトを変えることもできます。


使う場面

Delegateライブラリの素晴らしさは__setobj__に存在します。何が素晴らしいかというと、__setobj__でオブジェクトを変えるために、柔軟にロジックをスイッチすることができます。以下の例をご参照ください。

require 'delegate'

class TicketSeller
def sellTicket()
return 'Here is a ticket'
end
end

class NoTicketSeller
def sellTicket()
"Sorry-come back tomorrow"
end
end

class TicketOffice < SimpleDelegator
def initialize
@seller = TicketSeller.new
@noseller = NoTicketSeller.new
super(@seller)
end
def allowSales(allow = true)
__setobj__(allow ? @seller : @noseller)
allow
end
end

to = TicketOffice.new
to.sellTicket » "Here is a ticket"
to.allowSales(false) » false
to.sellTicket » "Sorry-come back tomorrow"
to.allowSales(true) » true
to.sellTicket » "Here is a ticket"

この例では、一つのTicketOfficeオブジェクトの#sellTicketメソッドに、二つのロジックを実現した:「チケットが売り切れ」と「チケットがある」。もちろんallowSalesのなかで if elseで実現するのも可能ですが、ロジックがややこしくなる場合は、別のオブジェクトに分けたいでしょう。そのときはDelegateを使えば柔軟にロジックの切り替えができると思います。

また、Delegateを使って、Strategyのデザインパターンも簡潔に実装できるて考えています。


まとめ

この記事ではRubyで委譲の使い方・使う場面を簡単にまとめました。

私は誤解、間違うところがあれば、是非気軽にコメントいただけると幸いです。

参考