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_delegator
とdef_delegators
が使え、上のようにどれかのオブジェクトに任せたいメソッドを定義すれば委譲することができます。
###使う場面
Forwardableモジュールを、いつ、どの場面で使えばよいかを結構不思議に思う方がいらっしゃるとおもいます。私は個人的に、Forwardableを使って簡潔にオブジェクトの部品化ができると思います。
例えば
「パソコン」というオブジェクト設計したい。
パソコンの機能は、
- タイピング
- 画像見える
見たいな機能を搭載した。
インターフェスは以下のように設計したい。
Computer.new.type
Computer.new.view_screen
だけど、すでにKeyboardとScreenというクラスが存在し、すでにKeyboard#type
とScreen#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_delegator
かdef_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で委譲の使い方・使う場面を簡単にまとめました。
私は誤解、間違うところがあれば、是非気軽にコメントいただけると幸いです。
参考