Как работать с именными коллизиями в 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 называется.

Другие вопросы по тегам