Не могу вызвать супер, когда monkeypatching Set # add - PullRequest
0 голосов
/ 16 мая 2018

Я хочу исправить Set#add, чтобы принять несколько аргументов, так же, как с Array#push.Следующее:

require 'set'
class Set
  def add(*args)
    args.each { |arg| super arg }
    self
  end
end
s = Set.new
s.add 1,2

не работает, оно вызывает:

'block in add': super: нет метода суперкласса `add 'для # (NoMethodError)

Я не понимаю, почему так должно быть, поскольку в источнике Set#add я увидел, что у него стандартное определение.Это такое простое определение, что я могу легко обойти, используя super:

require 'set'
class Set
  def add(*args)
    args.each { |arg| @hash[arg] = true }
    self
  end
end
s = Set.new
s.add 1,2

Почему не работает вызов super?

Ответы [ 2 ]

0 голосов
/ 16 мая 2018

Если вы переопределите метод, то вы измените его определение. Звонок super не будет работать; оригинальный метод не все еще существует как нечто для переадресации вызова сообщения.

Есть несколько способов решить эту проблему. «Классическим» способом было бы вообще не переопределять метод, а вместо этого определить свой собственный подкласс из Set:

require 'set'
class MySet < Set
 def add(*args)
    args.each { |arg| super arg }
    self
  end
end
s = MySet.new
s.add 1,2
#=> #<MySet: {1, 2}>

... Но это не может быть идеальным, так как вам нужно ссылаться на MySet (или как вы его называете) по всей базе кода, а не Set.

Если вы хотите изменить поведение Set напрямую, тогда "ru-way" старой школы ( больше не рекомендуется , по состоянию на ruby ​​v2.0+) - это alias оригинальный метод с другим именем, затем создайте свою собственную версию и вызовите оригинальный:

require 'set'
class Set
  alias_method :original_add, :add
  def add(*args)
    args.each { |arg| original_add(arg) }
    self
  end  
end
s = Set.new
s.add 1,2
#=> #<Set: {1, 2}>

Rails также используется для предоставления вспомогательного метода: alias_method_chain, что немного облегчает выполнение этого общего обходного пути.

Но очевидно, что это не очень хорошее решение, так как вы в конечном итоге загрязняете класс странными методами, такими как original_<foo> или <foo>_with(out)_<feature>. К счастью, в Ruby 2.0 добавлен новый метод: Module#prepend, который обеспечивает более чистое решение проблемы:

require 'set'
module AddWithMultipleArgs
  def add(*args)
    args.each { |arg| super arg }
    self
  end
end

class Set
  prepend AddWithMultipleArgs
end

# Or, you can just call:
# Set.prepend(AddWithMultipleArgs)

s = Set.new
s.add 1,2
#=> #<Set: {1, 2}>

Это работает путем введения вашего нового метода перед исходной версией в цепочке предков класса:

Set.ancestors
#=> [AddWithMultipleArgs, Set, Enumerable, Object, PP::ObjectMixin, Kernel, BasicObject]

Поскольку вы никогда не изменяли исходную версию метода, в самом классе Set вызов super будет работать, как вы ожидаете.

0 голосов
/ 16 мая 2018

Почему не получается звонить super?

Это потому, что Set#add определено непосредственно на Set, а не на любом из его суперклассов. И вы переписываете это определение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...