Как работать с именными коллизиями в ruby - PullRequest
0 голосов
/ 24 сентября 2018

Два модуля Foo и Baa соответственно определяют метод с одинаковым именем name, и я сделал include Foo и include Baa в определенном контексте.

Когда я вызываю name, как я могу определить, нужно ли вызывать метод name для Foo или Baa?

Ответы [ 4 ]

0 голосов
/ 25 сентября 2018

Вы должны обязательно прочитать о поиске методов.

В любом случае, я бы сделал это следующим образом:

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
0 голосов
/ 24 сентября 2018

Технически, нет конфликта имен, потому что метод 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
0 голосов
/ 24 сентября 2018

Если ни один из методов 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.

0 голосов
/ 24 сентября 2018

Только порядок включения модулей определяет, какой из них будет вызван.Не может быть обоих с одним и тем же именем - последнее переопределит первое.

Конечно, вы можете делать любые трюки, только из головы:

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

Ноэто не годится для реальных случаев использования:)

...