首页 文章

ruby继承vs mixins

提问于
浏览
122

在Ruby中,因为你可以包含多个mixin但只扩展一个类,所以看起来mixins比继承更受欢迎 .

我的问题:如果你正在编写必须扩展/包含的代码才有用,你为什么要把它作为一个类?换句话说,为什么你不总是把它变成一个模块?

我只能想到你想要一个类的一个原因,那就是你需要实例化这个类 . 但是,在ActiveRecord :: Base的情况下,您永远不会直接实例化它 . 所以不应该是一个模块而不是?

7 回答

  • 10

    我的看法:模块用于共享行为,而类用于建模对象之间的关系 . 从技术上讲,你可以把所有东西都作为Object的一个实例,并混合你想要获得所需行为的任何模块,但那将是一个糟糕的,偶然的,相当难以理解的设计 .

  • -1

    我刚刚在The Well-Grounded Rubyist阅读了这个主题(顺便说一下,这本书很棒) . 作者比我更好地解释,所以我引用他:


    没有单一的规则或公式总能产生正确的设计 . 但是,当您进行类与模块的决策时,记住一些注意事项是有用的:

    • 模块没有实例 . 因此,实体或事物通常最好在类中建模,并且实体或事物的特征或属性最好封装在模块中 . 相应地,如4.1.1节所述,类名往往是名词,而模块名通常是形容词(Stack与Stacklike) .

    • 一个类只能有一个超类,但它可以根据需要混合使用多个模块 . 如果您正在使用继承,请优先创建合理的超类/子类关系 . 不要使用类的唯一超级关系来赋予类可能只是几组特征之一的类 .

    在一个例子中总结这些规则,这是你不应该做的:

    module Vehicle 
    ... 
    class SelfPropelling 
    ... 
    class Truck < SelfPropelling 
      include Vehicle 
    ...
    

    相反,你应该这样做:

    module SelfPropelling 
    ... 
    class Vehicle 
      include SelfPropelling 
    ... 
    class Truck < Vehicle 
    ...
    

    第二个版本更加整洁地模拟实体和属性 . 卡车来自车辆(这是有意义的),而SelfPropelling是车辆的特征(至少,我们在这个世界模型中所关心的所有人) - 由于卡车是后代而传递给卡车的特征,或专门的车辆形式 .

  • 1

    我认为mixins是一个好主意,但是这里没有人提到另一个问题:命名空间冲突 . 考虑:

    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 Amodule B 's constants and methods are in unlikely namespaces. The problem is that the compiler doesn'都会发出警告 .

    我认为这种行为不适用于大型程序员团队 - 你不应该假设实现 class C 的人知道范围内的每个名字 . Ruby甚至可以让你覆盖不同类型的常量或方法 . 我不确定这可能被认为是正确的行为 .

  • 39

    我非常喜欢Andy Gaskell的答案 - 只是想补充一点是,ActiveRecord不应该使用继承,而是包含一个模块来将行为(主要是持久性)添加到模型/类中 . ActiveRecord只是使用了错误的范例 .

    出于同样的原因,我非常喜欢MongoId而不是MongoMapper,因为它让开发人员有机会使用继承作为在问题域中建模有意义的方法 .

    令人遗憾的是,Rails社区中几乎没有人按照它应该使用的方式使用“Ruby继承” - 定义类层次结构,而不仅仅是添加行为 .

  • 11

    你的问题的答案主要是语境 . 提炼pubb的观察结果,选择主要取决于所考虑的领域 .

    是的,ActiveRecord应该被包含在内而不是由子类扩展 . 另一个ORM - datamapper - 正是实现了这一目标!

  • 169

    我理解mixins的最佳方式是虚拟类 . Mixins是“虚拟类”,已注入类或模块的祖先链中 .

    当我们使用“include”并传递一个模块时,它会在我们继承的类之前将模块添加到祖先链中:

    class Parent
    end 
    
    module M
    end
    
    class Child < Parent
      include M
    end
    
    Child.ancestors
     => [Child, M, Parent, Object ...
    

    Ruby中的每个对象也都有一个单例类 . 添加到此singleton类的方法可以直接在对象上调用,因此它们充当“类”方法 . 当我们在一个对象上使用“extend”并将该对象传递给一个模块时,我们将该模块的方法添加到该对象的singleton类中:

    module M
      def m
        puts 'm'
      end
    end
    
    class Test
    end
    
    Test.extend M
    Test.m
    

    我们可以使用singleton_class方法访问singleton类:

    Test.singleton_class.ancestors
     => [#<Class:Test>, M, #<Class:Object>, ...
    

    当Ruby被混合到类/模块中时,它为模块提供了一些钩子 . included 是Ruby提供的一种钩子方法,只要在某个模块或类中包含模块,就会调用它 . 就像包含一样,有一个关联的 extended 钩子延伸 . 当模块被另一个模块或类扩展时,将调用它 .

    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
    

    如您所见,这个单独的模块是添加实例方法,“类”方法,并直接作用于目标类(在这种情况下调用a_class_method()) .

    ActiveSupport :: Concern封装了这种模式 . 这是使用ActiveSupport :: Concern重写的相同模块:

    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
    
  • 4

    现在,我正在考虑 template 设计模式 . 对模块感觉不对劲 .

相关问题