在使用 send 从 ruby 中的对象实例调用时指定父级 class

Specifying a parent class while using send to call from an instance of an object in ruby

因此,据我所知,在 ruby 中 simulate/have 多重继承的方法是通过模块(如果有 another/better 请告诉我),所以让我们说我有以下 structure/architecture:

class A
    def foo
        p "Foo from A"
    end
end

module B
    def foo
        p "Foo from B"
    end
end

class C < A
    include B
end

c = C.new

c.send('foo')

上面的代码你们很多人都知道会打印 Foo from B,因为 send 函数会在 class C 内部查找,因为没有foo 函数的定义它将在 C 祖先中寻找该函数,所以我的问题(也许不是一个聪明的问题),有没有办法 specify/prioritize [=15 的祖先=] 在调用 send/ 或该行为的解决方法时(不想实例化父 class 我需要这种方式)?我在 the send function.

的文档中找不到任何内容

已编辑

上面显示的架构是遗留代码,我的目的是找到一种解决方法,以免破坏现有代码(这就是我拥有该架构的原因)

提前致谢。

添加您的“首选”模块

不清楚您为什么要尝试以这种方式做事,因为您似乎已经找到了所需的解决方案,但没有明确定义要解决的问题。由于 A 和 B 都有一个 #foo 方法,因此首先调用哪个方法将取决于执行方法查找的顺序。例如,使用您当前的代码:

c.class.included_modules
#=> [B, Kernel]

c.class.ancestors
#=> [C, B, A, Object, Kernel, BasicObject]

在您当前的代码中,c.send 'foo' 将调用 B#foo,因为它将是查找中第一个可以 respond_to? :foo 的对象。更改查找的唯一方法是从 C class 中显式调用 A#foo,或者将 A 重写为模块并使用 Module#prepend 将其插入查找的前面 B 或 C 之前. 例如:

module A
    def foo; p 'Foo from A'; end
end

module B
    def foo; p 'Foo from B'; end
end

class C
    include B
    prepend A
end

C.ancestors
#=> [A, C, B, Object, Kernel, BasicObject]

C.new.foo
#=> "Foo from A"

问题来了:既然不想用B的方法,为什么还要包括B呢?我可以看到两种情况。

  • 您想挑选 A 和 B 的部分:它们做得太多了。
  • A#foo 和 B#foo 无关。

多重继承的问题在于您永远无法确定将调用哪个方法。模块减轻了这个问题,但因为它们污染了你的命名空间,问题仍然存在。 Duck typing 使情况变得更糟,如果两个不相关的模块恰好具有相同的名称,则缺少签名,它们将相互干扰。

你可以 ,但不能保证问题不会再次出现,而且祖先树仍然隐含和复杂,会招致未来的错误。

最好使用 delegation 来完全避免这种情况。

class B
  def foo
    p "Foo from B"
  end
end

class C < A
  attr_accessor :b

  def initialize
    @b = B.new
  end
end

现在您可以通过调用 c.b.foo 来消除您正在做的事情的歧义。如果要隐藏 b.

也可以重命名该方法
class C < A
  attr_accessor :b

  def initialize
    @b = B.new
  end

  def b_foo
    b.foo
  end
end

如果 A#foo 和 B#foo 相关,而你只想要 B 的 part,那么 B 做的太多了。从both A 和 B 提取冲突片段到一个单独的模块中。

例如,假设 A 与服务对话。 B 改变了 A 与服务的对话方式,但您想保留 A 的登录方法。从两者中提取登录部分。

class A
  # no login method
end

module A::Login
  def login
  end
end

module B::Login
  def login
  end
end

class C < A
  include A::Login
  include B
end

现在 C 从 A、B 和 A 样式登录中获取功能。

这确实意味着 A 的用户必须选择一个登录模块。同样,通过委派可能会更好地解决这个问题。

class A
  attr_writer :authenticator

  def authenticator_class
    A::Authenticator
  end

  # Defaults to A::Authenticator.new
  def authenticator
    @authenticator ||= authenticator_class.new
  end

  def login
    authenticator.login
  end
end

class A::Authenticator
  def login
  end
end

class B::Authenticator
  def login
  end
end

class C < A
  # Switch to using B for authentication.
  def authenticator_class
    B::Authenticator
  end
end

现在 A 有一个默认的身份验证,它可以 显式 覆盖整个 class 或单个对象。

c.authenticator = D::Authenticator.new

何时使用模块、子class或委托没有明确的答案。但是,当您遇到方法冲突时,可能是时候切换到委托了。

The architecture shown above is a legacy code, my intention is to find a workaround in order to not be disruptive with the existing code (that's why I have that architecture)

作为(快速而肮脏的)解决方法,您可以使用 super_method and call 调用 A#foo:

c = C.new
c.method(:foo).super_method.call
#=> "Foo from A"

您也可以使用循环动态地找到“正确”的方法:

m = c.method(:foo)
m = m.super_method until m.owner == A
m.call
#=> "Foo from A"

请注意,以这种方式绕过祖先并直接调用方法可能会导致意外结果。