module A; def a; end; end
module B; def b; end; end
class C; include A; end
module A; include B; end
class D; include A; end
C.new.b # undefined method error
D.new.b # nil
C.ancestors # [C, A, Object...]
D.ancestors # [D, A, B, Object...]
How to include module B inside of A, so that classes that already include module A will also get methods from module B?
如何在A中包含模块B,以便已经包含模块A的类也将从模块B获取方法?
3 个解决方案
#1
2
You can't.
When you include
a mixin M
into a class C
, Ruby creates a new class ⟦M′⟧
whose method table points to the method table of the mixin M
and whose superclass is the superclass of C
, then makes this class the new superclass of C
. This is repeated for every mixin that was mixed into M
.
当你将mixin M包含在C类中时,Ruby会创建一个新类⟦M'⟧,其方法表指向mixin M的方法表,其超类是C的超类,然后使该类成为C的新超类。对于混入M的每种混合物,重复这一过程。
Note that this algorithm is run only once, when you mix M
into C
. Modules that get include
d later, will not get considered.
请注意,当您将M混合到C中时,此算法仅运行一次。稍后将包含的模块将不会被考虑。
#2
3
As has already been stated, Ruby doesn't work like this - when a class includes a module, it doesn't maintain any reference to the instance of that module so if the module includes other modules, the classes that have already included it won't know about the change.
正如已经说过的那样,Ruby不会像这样工作 - 当一个类包含一个模块时,它不维护对该模块实例的任何引用,所以如果该模块包含其他模块,那么已经包含它的类就赢了不知道变化。
You could get around this by storing the classes that include the module in the module itself - that way you can update them whenever the module includes another module, i.e. something like this:
您可以通过将包含模块的类存储在模块本身中来解决这个问题 - 这样,只要模块包含另一个模块,您就可以更新它们,例如:
module A
class<<self
attr_accessor :observers
end
self.observers = []
def a
"#{self} successfully called a"
end
def self.included(base)
self.observers << base unless self.observers.include?(base)
end
def self.include(mod)
observers.each {|o| o.send(:include,mod) }
super
end
end
module B
def b
"#{self} successfully called b"
end
end
class C
include A
end
module A
include B
end
class D
include A
end
module E
def e
"#{self} successfully called e"
end
end
module A
include E
end
puts C.new.b
puts D.new.b
puts C.new.e
puts D.new.e
Not sure this is the direction you want to take, but shows that it can be done in principle.
不确定这是你想要的方向,但表明它原则上可以完成。
#3
1
You should Include B in A before class C; include A; end.
你应该在C级之前将A包含在A中;包括A;结束。
module A; def a; end; end
module B; def b; end; end
module A; include B; end
class C; include A; end
class D; include A; end
p C.new.b # nil
p D.new.b # nil
p C.ancestors # [C, A, B, Object, Kernel, BasicObject]
p D.ancestors # [D, A, B, Object, Kernel, BasicObject]
Edit
module A; def a; end; end
module B; def b; end; end
class C; include A; end
module A; include B; end
class D; include A; end
C.send(:include, A)
p C.new.b # nil
p D.new.b # nil
p C.ancestors # [C, A, Object...]
p D.ancestors # [D, A, B, Object...]
#1
2
You can't.
When you include
a mixin M
into a class C
, Ruby creates a new class ⟦M′⟧
whose method table points to the method table of the mixin M
and whose superclass is the superclass of C
, then makes this class the new superclass of C
. This is repeated for every mixin that was mixed into M
.
当你将mixin M包含在C类中时,Ruby会创建一个新类⟦M'⟧,其方法表指向mixin M的方法表,其超类是C的超类,然后使该类成为C的新超类。对于混入M的每种混合物,重复这一过程。
Note that this algorithm is run only once, when you mix M
into C
. Modules that get include
d later, will not get considered.
请注意,当您将M混合到C中时,此算法仅运行一次。稍后将包含的模块将不会被考虑。
#2
3
As has already been stated, Ruby doesn't work like this - when a class includes a module, it doesn't maintain any reference to the instance of that module so if the module includes other modules, the classes that have already included it won't know about the change.
正如已经说过的那样,Ruby不会像这样工作 - 当一个类包含一个模块时,它不维护对该模块实例的任何引用,所以如果该模块包含其他模块,那么已经包含它的类就赢了不知道变化。
You could get around this by storing the classes that include the module in the module itself - that way you can update them whenever the module includes another module, i.e. something like this:
您可以通过将包含模块的类存储在模块本身中来解决这个问题 - 这样,只要模块包含另一个模块,您就可以更新它们,例如:
module A
class<<self
attr_accessor :observers
end
self.observers = []
def a
"#{self} successfully called a"
end
def self.included(base)
self.observers << base unless self.observers.include?(base)
end
def self.include(mod)
observers.each {|o| o.send(:include,mod) }
super
end
end
module B
def b
"#{self} successfully called b"
end
end
class C
include A
end
module A
include B
end
class D
include A
end
module E
def e
"#{self} successfully called e"
end
end
module A
include E
end
puts C.new.b
puts D.new.b
puts C.new.e
puts D.new.e
Not sure this is the direction you want to take, but shows that it can be done in principle.
不确定这是你想要的方向,但表明它原则上可以完成。
#3
1
You should Include B in A before class C; include A; end.
你应该在C级之前将A包含在A中;包括A;结束。
module A; def a; end; end
module B; def b; end; end
module A; include B; end
class C; include A; end
class D; include A; end
p C.new.b # nil
p D.new.b # nil
p C.ancestors # [C, A, B, Object, Kernel, BasicObject]
p D.ancestors # [D, A, B, Object, Kernel, BasicObject]
Edit
module A; def a; end; end
module B; def b; end; end
class C; include A; end
module A; include B; end
class D; include A; end
C.send(:include, A)
p C.new.b # nil
p D.new.b # nil
p C.ancestors # [C, A, Object...]
p D.ancestors # [D, A, B, Object...]