首页 > 解决方案 > 在Ruby中的每个对象上覆盖诸如“#==”之类的方法?

问题描述

免责声明:这只是为了满足我的好奇心——我没有将它用于任何用途的计划。我意识到这很少是一个好主意!

我很想听听有关如何实现这一目标的任何想法,无论它们是否明智。

我怎样才能覆盖一个方法,例如#==在Ruby中的每个对象 ( ) 上?BasicObject

我可以覆盖它BasicObject

class BasicObject
  def ==(other); puts "custom for #{self} and #{other}!"; end
end

但是当子类也覆盖它时,这将不起作用:

true == false  # Runs my custom code!
"a" == "b"  # Doesn't!

# Because of this:
true.method(:==)  # => #<Method: TrueClass(BasicObject)#==(_)>
"hi".method(:==)  # => #<Method: String#==(_)>

前置BasicObject也无济于事 - 子类仍然获胜 - 前置模块在列表中太远了:

String.ancestors  # => [String, Comparable, Object, Kernel, MyPrependedMod, BasicObject]

我也可以手动覆盖它String——但是尝试在每个覆盖的类上手动执行它#==很容易出错,特别是因为任何人都可以随时在某些用户定义的类上覆盖它。

有哪些方法可以实现这一目标?

标签: ruby

解决方案


这是我想出的一个解决方案。需要明确的是,这很可能不应该添加到代码库中。这只是为了好玩。

ObjectSpace.each_object(Class) do |klass|
  next if klass.frozen?  # `Object` is frozen.

  klass.define_method(:==) { |other| puts "custom for #{self} and #{other}!" }
end

# These all work.
true == false
"a" == "b"
1 == 2
:hi == :bye

如果你想prepend(例如调用super你的覆盖),有一个问题:

ObjectSpace.each_object(Class) do |klass|
  next if klass.frozen?  # `Object` is frozen.

  # We can't use `prepend` here since e.g. `String` overrides it.
  # `prepend_features` is essentially the same thing.
  (Module.new do
    def ==(other); puts "custom for #{self} and #{other}!"; super; end
  end).send(:prepend_features, klass)
end

# These all work.
true == false
"a" == "b"
1 == 2
:hi == :bye

除了使用prepend_features,我们还可以确保调用BasicObject'sprepend而不是子类中覆盖的那个:

# Some classes, like `String`, override "prepend". 
prep = BasicObject.method(:prepend).unbind

ObjectSpace.each_object(Class) do |klass|
  next if klass.frozen?  # `Object` is frozen.

  prep.bind(klass).call(Module.new do
    def ==(other); puts "custom for #{self} and #{other}!"; super; end
  end)
end

# These all work.
true == false
"a" == "b"
1 == 2
:hi == :bye

推荐阅读