13
11

More than 5 years have passed since last update.

Rubyで委譲…forwardable

Posted at

オブジェクト指向プログラミングでは、「委譲」と呼ばれる手法があります。今回は、Rubyで委譲を行う'forwardable'について触れてみます。

利用例

たとえば、親クラス(Parentとします)があるハッシュをメンバに持っていて、しかも

  • Parentからは、このハッシュを変更できる。
  • ハッシュ自体は外部で変更されたくはないが、読み取り用としてはハッシュとして外部でも使って欲しい
  • 外部から使うたびにコピーするにはハッシュが大きすぎる

というような状況にあるとします。このようなときに、「委譲」を使うことができます。

forwardableモジュール

Rubyに標準添付されているforwardableというライブラリがあって、「指定されたメソッドを、特定のオブジェクトに実行させる」ことができます。クラスにextend Forwardableとすることで、attr_accessorのようなクラスメソッドとして、def_delegatorsが使えるようになります。

def_delegators
class Foo
  extend Forwardable
  def_delegators :@item, :method_1, :method_2 #...
end

このようにすると、Fooクラスのオブジェクトにmethod_1method_2のようなメソッドができて、それはインスタンス変数の@itemへの呼び出しになります。

includesとの併用

このような形で委譲をかける場合に、EnumerableComparableがあるクラスですべてをやろうとすると、手間になってしまいます。ただ、Enumerableeachだけあれば、Comparable<=>だけあれば動くので、そのキーになるメソッドだけ委譲して、残りは直接includeしてしまう形で問題ありません。

include
class Bar
  extend Forwardable
  include Comparable
  include Enumerable
  #includeしたのを動かすのに必要十分なメソッドだけ借りてくる
  def_delegators :@item, :each, :<=>
end

最初の例の実装

では、最初のユースケースについてフル実装します。

ReadOnlyHash
require 'forwardable'

class Parent
  class ReadOnlyHash
    extend Forwardable
    include Enumerable
    # 破壊的変更をしないメソッドだけ読み込む
    def_delegators :@hash, :each, :[], :fetch, :empty?, :clone, :dup
    def_delegators :@hash, :each_pair, :each_key, :each_value, :keys, :values
    def_delegators :@hash, :has_key?, :key?, :include?
    # 多いので以下省略

    def initialize(hash)
      @hash = hash
    end
  end

  #内部的にいろいろ操作する
  @hash = {  }

  #読み取りしかできないものを返す
  def hash
    ReadOnlyHash.new @hash
  end

end

その他

これはHashfreezeした場合にも通じますが、ハッシュの中身が文字列やコンテナなどの場合、要素への破壊的変更は行えてしまいますので、その点には注意が必要です。

13
11
0

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
13
11