在使用 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"
请注意,以这种方式绕过祖先并直接调用方法可能会导致意外结果。
因此,据我所知,在 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"
请注意,以这种方式绕过祖先并直接调用方法可能会导致意外结果。