LoginSignup
42
41

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-09-01

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で委譲の使い方・使う場面を簡単にまとめました。
私は誤解、間違うところがあれば、是非気軽にコメントいただけると幸いです。

参考

42
41
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
42
41