module A
HELLO = "hi"
def sayhi
puts HELLO
end
end
module B
HELLO = "you stink"
def sayhi
puts HELLO
end
end
class C
include A
include B
end
c = C.new
c.sayhi
哪一个获胜?在Ruby中,事实证明是后者, module B ,因为你在 module A 之后包含它 . 现在,很容易避免这个问题:确保碰撞发生时,所有 module A 和 module B 's constants and methods are in unlikely namespaces. The problem is that the compiler doesn'都会发出警告 .
我认为这种行为不适用于大型程序员团队 - 你不应该假设实现 class C 的人知道范围内的每个名字 . Ruby甚至可以让你覆盖不同类型的常量或方法 . 我不确定这可能被认为是正确的行为 .
module M
def self.included(target)
puts "included into #{target}"
end
def self.extended(target)
puts "extended into #{target}"
end
end
class MyClass
include M
end
class MyClass2
extend M
end
这创建了一个开发人员可以使用的有趣模式:
module M
def self.included(target)
target.send(:include, InstanceMethods)
target.extend ClassMethods
target.class_eval do
a_class_method
end
end
module InstanceMethods
def an_instance_method
end
end
module ClassMethods
def a_class_method
puts "a_class_method called"
end
end
end
class MyClass
include M
# a_class_method called
end
module M
extend ActiveSupport::Concern
included do
a_class_method
end
def an_instance_method
end
module ClassMethods
def a_class_method
puts "a_class_method called"
end
end
end
7 回答
我的看法:模块用于共享行为,而类用于建模对象之间的关系 . 从技术上讲,你可以把所有东西都作为Object的一个实例,并混合你想要获得所需行为的任何模块,但那将是一个糟糕的,偶然的,相当难以理解的设计 .
我刚刚在The Well-Grounded Rubyist阅读了这个主题(顺便说一下,这本书很棒) . 作者比我更好地解释,所以我引用他:
没有单一的规则或公式总能产生正确的设计 . 但是,当您进行类与模块的决策时,记住一些注意事项是有用的:
模块没有实例 . 因此,实体或事物通常最好在类中建模,并且实体或事物的特征或属性最好封装在模块中 . 相应地,如4.1.1节所述,类名往往是名词,而模块名通常是形容词(Stack与Stacklike) .
一个类只能有一个超类,但它可以根据需要混合使用多个模块 . 如果您正在使用继承,请优先创建合理的超类/子类关系 . 不要使用类的唯一超级关系来赋予类可能只是几组特征之一的类 .
在一个例子中总结这些规则,这是你不应该做的:
相反,你应该这样做:
第二个版本更加整洁地模拟实体和属性 . 卡车来自车辆(这是有意义的),而SelfPropelling是车辆的特征(至少,我们在这个世界模型中所关心的所有人) - 由于卡车是后代而传递给卡车的特征,或专门的车辆形式 .
我认为mixins是一个好主意,但是这里没有人提到另一个问题:命名空间冲突 . 考虑:
哪一个获胜?在Ruby中,事实证明是后者,
module B
,因为你在module A
之后包含它 . 现在,很容易避免这个问题:确保碰撞发生时,所有module A
和module B
's constants and methods are in unlikely namespaces. The problem is that the compiler doesn'都会发出警告 .我认为这种行为不适用于大型程序员团队 - 你不应该假设实现
class C
的人知道范围内的每个名字 . Ruby甚至可以让你覆盖不同类型的常量或方法 . 我不确定这可能被认为是正确的行为 .我非常喜欢Andy Gaskell的答案 - 只是想补充一点是,ActiveRecord不应该使用继承,而是包含一个模块来将行为(主要是持久性)添加到模型/类中 . ActiveRecord只是使用了错误的范例 .
出于同样的原因,我非常喜欢MongoId而不是MongoMapper,因为它让开发人员有机会使用继承作为在问题域中建模有意义的方法 .
令人遗憾的是,Rails社区中几乎没有人按照它应该使用的方式使用“Ruby继承” - 定义类层次结构,而不仅仅是添加行为 .
你的问题的答案主要是语境 . 提炼pubb的观察结果,选择主要取决于所考虑的领域 .
是的,ActiveRecord应该被包含在内而不是由子类扩展 . 另一个ORM - datamapper - 正是实现了这一目标!
我理解mixins的最佳方式是虚拟类 . Mixins是“虚拟类”,已注入类或模块的祖先链中 .
当我们使用“include”并传递一个模块时,它会在我们继承的类之前将模块添加到祖先链中:
Ruby中的每个对象也都有一个单例类 . 添加到此singleton类的方法可以直接在对象上调用,因此它们充当“类”方法 . 当我们在一个对象上使用“extend”并将该对象传递给一个模块时,我们将该模块的方法添加到该对象的singleton类中:
我们可以使用singleton_class方法访问singleton类:
当Ruby被混合到类/模块中时,它为模块提供了一些钩子 .
included
是Ruby提供的一种钩子方法,只要在某个模块或类中包含模块,就会调用它 . 就像包含一样,有一个关联的extended
钩子延伸 . 当模块被另一个模块或类扩展时,将调用它 .这创建了一个开发人员可以使用的有趣模式:
如您所见,这个单独的模块是添加实例方法,“类”方法,并直接作用于目标类(在这种情况下调用a_class_method()) .
ActiveSupport :: Concern封装了这种模式 . 这是使用ActiveSupport :: Concern重写的相同模块:
现在,我正在考虑
template
设计模式 . 对模块感觉不对劲 .