オブジェクト指向プログラミングでは、「委譲」と呼ばれる手法があります。今回は、Rubyで委譲を行う'forwardable'について触れてみます。
利用例
たとえば、親クラス(Parent
とします)があるハッシュをメンバに持っていて、しかも
-
Parent
からは、このハッシュを変更できる。 - ハッシュ自体は外部で変更されたくはないが、読み取り用としてはハッシュとして外部でも使って欲しい
- 外部から使うたびにコピーするにはハッシュが大きすぎる
というような状況にあるとします。このようなときに、「委譲」を使うことができます。
forwardable
モジュール
Rubyに標準添付されているforwardable
というライブラリがあって、「指定されたメソッドを、特定のオブジェクトに実行させる」ことができます。クラスにextend Forwardable
とすることで、attr_accessor
のようなクラスメソッドとして、def_delegators
が使えるようになります。
class Foo
extend Forwardable
def_delegators :@item, :method_1, :method_2 #...
end
このようにすると、Foo
クラスのオブジェクトにmethod_1
やmethod_2
のようなメソッドができて、それはインスタンス変数の@item
への呼び出しになります。
includes
との併用
このような形で委譲をかける場合に、Enumerable
やComparable
があるクラスですべてをやろうとすると、手間になってしまいます。ただ、Enumerable
はeach
だけあれば、Comparable
は<=>
だけあれば動くので、そのキーになるメソッドだけ委譲して、残りは直接include
してしまう形で問題ありません。
class Bar
extend Forwardable
include Comparable
include Enumerable
#includeしたのを動かすのに必要十分なメソッドだけ借りてくる
def_delegators :@item, :each, :<=>
end
最初の例の実装
では、最初のユースケースについてフル実装します。
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
その他
これはHash
をfreeze
した場合にも通じますが、ハッシュの中身が文字列やコンテナなどの場合、要素への破壊的変更は行えてしまいますので、その点には注意が必要です。