module N
attr_accessor :v
end
class C
include N
# def initialize
# @v = 1
# end
def p_var
p @v
end
end
我想在N
中初始化我@v
而不是C
中
应该怎么做?
首先你应该明白,module 是 Ruby 中实现多继承的一个方式。 既然是一个继承,那么可以这样:
module N attr_accessor :v
def initialize @v = "hello" end end
class C include N def initialize super end end
c = C.new c.v
class C 里面的 initialize 方法可省,这里是为了说明,你还可以在 class C 中添加其他的变量。
#1 楼 @blackanger 这样实现有个弊端:
module N
attr_accessor :v
def self.included(base)
base.instance_eval do
define_method :initialize do
@v = 'hello'
end
end
end
end
class C
include N
end
c = C.new
puts c.v
@simlegate 你看看 这样能不能满足你的 需求..
#7 楼 @meeasyhappy 我发现你的实现方式和#1 的没有差别,就是动态为 C 定义了 initialize 方法。
# 当我
class C
include N
def initialize
end
end
c = C.new
puts c.v # => nil
@simlegate module 和 class 的关系就是 class 继承了 module,不太明白你说的“不想 module 和 class 之间有很紧密的关系”,而且我的方法,也不依赖于 C class 啊,你可以把初始变量都放到 module 里面,C class 里面可以放只是和 C 相关的初始变量,M 里面放公共的初始变量,假如你还有个 Class D,include M 之后,也可以使用@v。
「补充」:如果你用@meeasyhappy那种方式,利用了 included 回调来动态定义方法,除外。
毕竟 mixin 不是继承,当又有 include 又有继承的时候,super 调到哪里就比较难弄清楚了
笨办法:把 N 的初始化改个名字
module N
attr_accessor :v
def init_n
@v = ...
end
end
class C
include N
def initialize
...
init_n
...
end
end
当有多个 "上一层" 的时候,super 的语义就很牵强了
class C < B
include N
include M
end
mixin 更倾向于用组合 (composition) 而非继承的方式去理解问题,例如:
module Hand
end
module Leg
end
module Doll
include Hand
include Leg
end
Hand 是 Doll 的一部分,你偏要说 Doll 继承了 Hand 不就很奇怪么...
Ruby 里说到的继承就是指 class A < B
这种语法结构,和 mixin 不同... 当多继承用只是 mixin 的一个用途而已,但是这个 "多继承" 和 Ruby 语法里的 "继承" 不是一回事,这么讲会产生混淆的...
调 super
的一个问题就是菱形继承
module A
def initialize
puts 'a'
end
end
module B
include A
end
module C
include A
def initialize
puts 'c'
end
end
class D
include C
include B
def initialize
super # 明显? 谁记住父类方法查找是深度优先还是广度优先?
end
end
另一个隐患是 open class, 在上面的代码之外,再加上这个:
module E
def initialize
puts 'e'
end
end
class D
include E
end
如果这两段代码载入顺序不同,结果也不同...
#20 楼 @luikore 首先,我说的是多重继承,不是继承(单一),是两个概念。
D.ancestors => [D, B, C, A, Object, PP::ObjectMixin, Kernel, BasicObject]
使用 ancestors 方法,可以看到方法查找的顺序,super 肯定是调用 C 的方法。
如果再加一个 E module,同样,D.ancestors 可以看到: [D, E, B, C, A, Object, PP::ObjectMixin, Kernel, BasicObject]
这次是调用 E 类的方法。
这和载入顺序没有关系,是和 ancestors 以及方法查找有关系的。
当你看到 ancestors 继承树,难道还能被所谓的菱形问题困扰吗? 这方面,可以参考《松本行弘的程序世界》一书中,Matz 对 Mix-in 的论述。
我明白你说的,咱俩说的是两个层面:一个是语言实现的本质,mixin 要解决的问题,一个是你作为程序员该如何使用,毕竟你把 initialize 方法换个名字 init_n 和 super 也没啥区别。但是如果了解这个实质,大家也不会有那种困扰了,但是为了代码的可读,使用你的方法很好。总的来说,和你讨论,受益良多。
class B < A
include C
end
这个里用 super 就已经很混乱了
而且 C 里可能用了ActiveSupport::Concern
之类的东西,难道你每次调用都翻遍 ancestors
从语义上 mixin 不是继承,不要从实现来理解
@jjym 好吧,随便你怎么理解吧。我也不是和谁去辩论和抬杠。
那你告诉我语义上 MixIn 是什么,组合?组合该怎么理解,你如何写出一个组合的代码?组合如何求解 @luikore 所说的那种菱形问题?
再一个,我说的是多重继承,不是继承(单继承那种继承,Ruby 里说的继承是指单继承)。是 Matz 的《松本行弘的程序世界》里告诉我的,Matz 不会写书瞎说吧?
所谓的混乱,只是在使用的时候,我们自己把自己搞混。你们说的组合,只是编程方法而已。把 initiliaze 方法换成 init_n,只是在可读性,维护性上更好了,我也认同@luikore的那种写法。我只是在说明 Mix-in 的行为是什么,而@luikore说不是多继承,如果不是多继承,他的例子里 class C 的 initialize 方法中调用的 init_n 哪来的?本身多继承和单继承其实也是同一个概念,就是继承嘛。
ActiveSupport::Concern 这种东西是 Rails 里实现的语法糖。
#3 楼 @meeasyhappy 请教这段代码
def self.included(base)
base.instance_eval do
define_method :initialize do
@v = 'hello'
end
end
end
为什么使用 instance_eval 和 class_eval 结果是一样的?C.instance_eval 不是定义的类方法?
@simlegate @alan90121 你俩人的回答真诡异。 [修改] define_method 是定义的 initialize 方法啊。 instance_eval 可以在 api 里去看看说明:http://ruby-doc.org/core-2.0/BasicObject.html#method-i-instance_eval Evaluates a string containing Ruby source code, or the given block, within the context of the receiver (obj).
是在 receiver 的上下文执行一段文本。
在 module 里用那个 instance_eval 实际上只是用了 included 回调方法,而 initialize 方法是定义到 Class C 里面的。这样的话,就和我 1 楼的例子不是同一个概念了,因为@meeasyhappy的方法是在 include 的时候利用回调函数动态定义了方法。
不得不再一次感慨,Ruby 真灵活!
在使用 include Mix-in 模块的时候,确实存在顺序问题,但也无法改变 Mix-in 是多重继承这个事实啊。
在 Ruby2.0 里新增的 prepend 方法(组合的概念怎么理解这个?),是可以把一个 module 变成类的子类的(在继承树关系上)。
所以,说的更精确一点就是: Ruby 中用 Mix-in 操作继承树来解决多继承的问题,正如 Matz 所说,Mix-in 让多继承的类结构变的简单了。 可以很确定的说,Matz 设计 Mix-in 的这种机制,就是为了让程序员可以像单继承那样去使用多继承。
至于你怎么利用 Mix-in,那就是你自己的事情了。
[增加 prepend 的代码]
class Foo
def do_stuff
puts "doing stuff"
end
end
module Wrapper
def do_stuff
puts "before stuff"
super
puts "after stuff"
end
end
class Foo
prepend Wrapper
end
Foo.new.do_stuff
#32 楼 @blackanger 我问点的是下面这段代码与@simlegate 的区别:
def self.included(base)
base.instance_eval do
def initialize do
@v = 'hello'
end
end
end
具体是这个帖子:http://ruby-china.org/topics/7854 忘记 define_method 确实是定义实例方法的,@simlegate 提醒了我。
比较倾向于把 module 单纯的当做 methods 的集合,Mixin 只是混入一些操作,至于维护状态的事情还是留给 Class 来做就。
如果实在需要 Module 维护状态,就显式定义一个 class macro, 在被混入的类里面明确调用,比如act_as_taggable
多继承的说法,让我想起了 C++ 里面的多继承关系,那叫一个纠结啊....
#35 楼 @nickelchen 去看 Matz 的《松本行弘的程序世界》一书吧,从 Matz 如何设计 Ruby 里面可以看出,Mix-in 正好解决了传统的多继承的问题,降低了复杂性,就像我楼上说的,Matz 设计 Mix-in 的这种机制,就是为了让程序员可以像单继承那样去使用多继承。所以不要和 C++ 的多继承去比,如果一定要比,Ruby 的实现和 Java 的更类似。
只有理解了这个实质,写代码的时候才不容易被 Mix-in 搞晕啊。
#37 楼 @simlegate 因为 Ruby 太灵活了啊,如果不把握一些实质,很容易被 Ruby 搞晕。所以我推荐去看 Matz 的书,看他设计 Ruby 的初衷和原则,对于我们理解 Ruby 里面的概念很有帮助。这可比看源码实际多了。
#39 楼 @simlegate 多看几遍吧,尤其是前几章讲他设计 Ruby 的过程。这本书不是教你 Ruby 语法的。比如第 30 页和 31 页,你就可以看到 Mix-in 和多重继承的关系,引导你解决一些问题。你就不会在 module 和 class 的关系上感到迷惑了。「原文:对 Mix-in 的理解是,Mix-in 只不过是实现多重继承的一个技巧而已。Ruby 只支持 Mix-in 形式的多重继承。Matz 也说了,曾有一个著名的青年学者因为 Ruby 的原因而误认为多重继承和 Mix-in 是不同的概念,这个让 Matz 有点惭愧,他觉得这些误解部分是他造成的,哈哈。」
还有很多地方: 「Mix-in 是使类结构变的简单的优秀技术。Mix-in 使多重继承的类形成非常清晰的树结构,没有变成网状结构。」如果非要使用组合的概念去理解 Mix-in,正好和 Matz 设计 Mix-in 的初衷相悖,让 Matz 情以何堪。
和载入顺序当然有关系了,先载入 E 的话,ancestors 顺序就不同了。我说的是编程时不好确定调用到哪里,你说的是运行时这个东西是确定的,不是一回事...
你看,当多继承去理解的话,首先得澄清"继承"的概念,要说明计算机里的"继承"和分类学中的概念不同,它还可以泛化到整体 < 部分
之类的用途。然后还要澄清多继承的概念,区分好规格继承和实现继承。再然后要解释为什么 Ruby 被分类为单继承的语言 (因为 Ruby 规格单继承,实现多继承). 再然后要解释清楚为什么不直接用 mixin 代替单继承的语法 (mixin 的四个限制)... matz 的书就用了大半章去澄清这里的坑...
同样是 matz 的书里写的 (35 页):
多重继承相当于语言功能支持模块组合
所以绕了一个圈又跑回来变成 composition 啦...
我觉得这里没有谁更本质的地方,你可以把 mixin 中各部分态射到多继承中的对应概念,表明两者实质上没有不同,但就组合去理解对写出来的代码没影响,而且对面向对象设计提供了便利的准则:分类就用规格继承,差分/组合就用 mixin
继承这个词含义太多了,我还是希望避免增加它的外延... 就像这个帖子的讨论一样,我说 mixin 不是 (规格) 继承,你说 mixin 是 (实现) 继承,没意义...
module Foo
attr_accessor :foo
def foo
@foo ||= 'zzz'
end
end
class Bar
include Foo
end
#46 楼 @luikore 本来就是继承嘛,为什么要避免呢, 《松本行弘的程序世界》31 页里面 Matz 解释的很清楚嘛。或者说,Mix-in 是 Ruby 里实现多重继承的方式,而且通过 Mix-in 这种方式,降低了传统多重继承的复杂性,更进一步说,Mix-in 是经过 Matz 改良的 Ruby 里实现多重继承的方式。
你说 35 页里写:多重继承相当于语言功能支持模块组合。
原文里还有前面一句话,假如把类当模块来看的话。而且,那句话的章节标题就是:动态编程语言也需要多重继承。
现在是模块是类,Mix-in 模块算是一种抽象类。它只是有限制,都是 Class 类的子类。
Matz 那本书里第二章基本通篇在说继承和多重继承与 Mix-in 的。
我为什么要和你讨论呢,就是因为你说「Mix-in 不是继承」,可它明明就是继承,你为什么要避免增加它外延,我也没有增加其外延,我说的就是多重继承,多重继承不也是一种继承嘛。Ruby 里的继承一般是指类和类之间的关系,而类和多个模块的时候,就是 Mix-in,多重继承。
所以我说,Mix-in 的实质是多重继承的一个实现方式。这就是讨论的意义所在。
你说的这句,[我说的是编程时不好确定调用到哪里,你说的是运行时这个东西是确定的] ,我也认同,楼上就认同了,我说你阐述的是一种编程方法,但是你用编程方法来理解 Mix-in 的工作原理实现方式,我就不认同了。
而且,matz 也说了,继承就是利用模块的方法(34 页)。
我说 Mix-in 是一种继承,你说继承是一种组合方式。
@luikore 再补充一点:你在楼上举的那个例子,所谓的菱形问题,是个伪问题,Ruby 里根本不存在这样的问题。
多重继承是描述类之间的关系,而组合,描述的是对象之间的关系,当然,如果你只从方法集合这个层面来说,Mix-in 是一种组合方式,也能说的通。
继承是 is_a,组合是 has_a, 你把 Module 理解为一个方法集合,当然是组合了,这个类 include 了 module,它的对象就可以 has_a 这个方法,has_a 那个方法。
而且 Module 本身是 Class 的父类, Class.superclass #=> Module
include 也是在 Module 中被定义的私有方法,如果你 include 一个模块,那么会把这个模块加到这个类的继承树里,请问用组合的概念如何解释这一行为?
所以得出的结论是,如果你只把 Mix-in 当作一个方法集合,而不关注类和模块的关系,那可以理解为组合,但这只是表面,当然理解到这一层,不影响编写代码了。但可能会碰到奇怪的问题,比如你举的那种菱形问题,会让你迷惑。
真正理解了 Mix-in 是多重继承(不是传统的多重继承,继承关系是单一的,又有多重继承的共用方法,不要用 C++ 那种多重继承去理解)这个概念, 「你说的:编程时不好确定调用到哪里」,这种问题也不会存在了。
#20 楼 我举的两段代码如果分散在 a.rb 和 b.rb, 然后它们是由 c.rb 载入的,编程者就必须把这 3 个文件都看了然后想明白了才能知道真正的调用顺序,是真正影响程序的理解和可读性的问题。如果 c.rb 是用 Dir.glob '*.rb'
之类的方法来获取 a.rb 和 b.rb 的路径然后载入,甚至会因为文件系统的差别而导致行为的差别,是绝对不可忽视的坑...
如果像 rails 那样通过 const_missing
去载入,甚至会因为访问 url 的顺序不同而产生不同的结果,不是这么简单就能解决的事情。
而这种坑多多少少就是因为从继承的角度去设计模块造成的...
其实,我说的就是,在 Ruby 里,你随便混入,他们都在一个继承树上,不会乱。 而你是提醒了别人,组合的时候注意载入顺序,或者使用定义可读的方法名来避免把程序员自己搞混。
「增加:」所以,我说的‘实质’就是,不管你怎么组合,他们都挂在一个继承树上。
剩下的,就看大家怎么去理解了。