Как работать с именными коллизиями в ruby
Два модуля Foo
а также Baa
соответственно определите метод с тем же именем name
, и я сделал include Foo
а также include Baa
в определенном контексте.
Когда я звоню name
как можно однозначно назвать ли name
метод Foo
или же Baa
?
4 ответа
Только порядок включения модулей определяет, какой из них будет вызван. Не может быть обоих с одним и тем же именем - последнее переопределит первое.
Конечно, вы можете делать любые трюки, прямо из головы:
module A
def foo
:foo_from_A
end
end
module B
def foo
:foo_from_B
end
end
class C
def initialize(from)
@from = from
end
def foo
from.instance_method(__method__).bind(self).call
end
private
attr_reader :from
end
C.new(A).foo #=> :a_from_A
C.new(B).foo #=> :a_from_B
Но это не годится для реальных случаев использования:)
Вы должны определенно прочитать о поиске методов.
Во всяком случае, я бы сделал это так:
module Foo
def name
:foo
end
end
module Bar
def name
:bar
end
end
class MyClass
include Foo
include Bar
def foo_name
Foo.instance_method(:name).bind(self).call
end
def bar_name
Bar.instance_method(:name).bind(self).call
end
#
# or even like this: obj.name(Foo)
#
def name(mod)
mod.instance_method(:name).bind(self).call
end
end
Кстати, если вы используете Module#instance_method
а также UnboundMethod#bind
вам не нужно включать конкретный модуль. Этот код работает:
Foo.instance_method(:name).bind('any object (e.g. string)').call
Технически, нет конфликта имен, потому что метод foo переопределен.
В следующем примере A.foo переопределяется и никогда не вызывается
module A
def foo
raise "I'm never called"
end
end
module B
def foo
puts :foo_from_B
end
end
class C
include A
include B
end
C.new.foo
# =>
# foo_from_B
Если вы пишете модуль A и B, вы можете использовать super для вызова предыдущего определения foo. Как будто это где унаследованный метод.
module A
def foo
puts :foo_from_A
end
end
module B
def foo
super
puts :foo_from_B
end
end
class C
include A
include B
end
C.new.foo
# =>
# foo_from_A
# foo_from_B
Есть побочные эффекты, и я бы не использовал это, но это делает свое дело:
module A
def foo
puts :foo_from_A
end
end
module B
def foo
puts :foo_from_B
end
end
class C
def self.include_with_suffix(m, suffix)
m.instance_methods.each do |method_name|
define_method("#{method_name}#{suffix}", m.instance_method(method_name))
end
end
include_with_suffix A, "_from_A"
include_with_suffix B, "_from_B"
end
c= C.new
c.foo_from_A
c.foo_from_B
begin
c.foo
rescue NoMethodError
puts "foo is not defined"
end
# =>
# foo_from_A
# foo_from_B
# foo is not defined
При условии, что ни один из методов Foo
или же Baa
вызов name
(что кажется разумным предположением), можно просто создать псевдонимы.
module Foo
def name; "Foo#name"; end
end
module Baa
def name; "Baa#name"; end
end
class C
include Foo
alias :foo_name :name
include Baa
alias :baa_name :name
undef_method :name
end
c = C.new
c.foo_name
#=> "Foo#name"
c.baa_name
#=> "Baa#name"
C.instance_methods & [:foo_name, :baa_name, :name]
#=> [:foo_name, :baa_name]
Ключевое слово alias
задокументировано здесь. В качестве альтернативы можно использовать метод #alias_method. Смотрите этот блог для сравнения двух.
Модуль #undef_method не является строго необходимым. Это просто для того, чтобы исключение возникало, если name
называется.