Posted at

同じメソッドをインスタンスメソッドにもクラスメソッドにも利用したい場合

More than 1 year has passed since last update.


移譲

module Forwardableを使います。

ForwardableはRubyの標準ライブラリです。

標準添付ライブラリ紹介 【第 6 回】 委譲


オブジェクトの機能を再利用する手法の一つとして、Ruby では言語仕様としてクラスの継承とモジュールの Mix-in を提供しています。これらは、元になるクラスやモジュールの実装までもをそのまま取り込んでしまいますが、他の手段で機能の再利用を実現する手法として、委譲があります。

委譲では、再利用したい機能を自分に取り込むのではなく、その機能を持つオブジェクトに処理を依頼します。

Ruby では特に言語仕様として委譲がサポートされているわけではありませんが、委譲を実現するためのライブラリとして forwardableとdelegate が用意されています。具体的には、これらのライブラリを使用することによって、あるメソッド呼び出しを他のオブジェク>トのメソッドにたらい回すということを簡単に記述することができます。



インスタンスメソッドに移譲

class Outer

extend Forwardable
def_delegators :@inner, :hello

def initialize
@inner = Inner.new
end

class Inner
extend Forwardable
def_delegators :hello

def hello
puts "hello, world!!"
end
end
end

obj = Outer.new
obj.hello #=> "hello, world!!"

Outer::InnerのインスタンスメソッドをOuterのインスタンスメソッドに移譲したので、どちらもインスタンスメソッドです。

Outer.instance_methods(false)

#=> [:hello]
Outer::Inner.instance_methods(false)
#=> [:hello]


クラスメソッドに移譲

以下のサンプルでは、Innerクラスの中身は全く変わっていないことに注目してください。

require 'forwardable'

class Outer
class << self
extend Forwardable
def_delegators :inner, :hello

def inner
Inner.new
end
end

class Inner
extend Forwardable
def_delegators :hello

def hello
puts "hello, world!!"
end
end
end

Outer.hello #=> hello, world!!

Innerクラスのhelloメソッドはプライベートメソッドですが、Outerクラスではクラスメソッドになっています。

Outer.methods(false)

#=> [:inner, :hello]
Outer::Inner.methods(false)
#=> []
Outer::Inner.instance_methods(false)
#=> [:hello]


さいごに

https://github.com/willnet/gimei

gimeiのコードを追っていて、どうしてincludeextendを普通に使わないのだろう?

と思ったのがこの記事を書くきっかけとなりました。

gimeiを詠む