红宝石继承VS混入(ruby inheritance vs mixins)

2019-06-18 11:36发布

在Ruby中,因为你可以包括多个混入但只能继承一个类,好像混入将优于继承。

我的问题:如果你正在写的代码必须扩展/纳入有用,为什么你会永远做它的一类? 或者换句话说,你为什么不总是使它成为一个模块?

我只能想到一个理由,为什么你会想一类,那就是如果你需要实例化的类。 在ActiveRecord的::基地的情况,但是,你永远无法直接实例它。 所以应该不是是一个模块呢?

Answer 1:

刚刚看了一下这个话题的良好接地Rubyist (伟大的书,顺便说一句)。 笔者做了解释比我所以我引用他的一个更好的工作:


没有一个单一的规则或公式结果总是在正确的设计。 但它是有用的,以保持一对夫妇记住,当你正在做类抗模块决策的考虑:

  • 模块没有实例。 由此可见,实体或东西通常在班最好建模,并且特征或实体或事物的属性在模块最好封装。 相应地,如在章节4.1.1指出,类名往往是名词,而模块名称往往形容词(栈与Stacklike)。

  • 一个类只能有一个超类,但它希望它可以混合在尽可能多的模块。 如果您使用继承,创造一个明智的超/子关系放在首位。 不要使用一个类的唯一超关系赋予什么可能变成只是几套特性的一个类。

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

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

相反,你应该这样做:

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

第二个版本车型的实体和属性更加整齐。 卡车从车辆下降(这是有道理的),而SelfPropelling是车辆的特性(至少,那些我们在这个模型世界的关心),其凭借卡车是一个后裔到卡车通过-a特点,或特殊形式,车辆。



Answer 2:

我想混入是一个伟大的想法,但有没有人提到这里的另一个问题:命名空间冲突。 考虑:

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的常量和方法都是不可能的命名空间。 问题是,编译器不会发出警告,在所有碰撞时发生。

我认为,这种行为不能扩展到大型团队的programmers--你不应该假设执行者class C知道的范围大约每名。 红宝石甚至会让你覆盖不同类型的常量或方法。 我不知道这能否被认为是正确的行为。



Answer 3:

我的看法:模块是为分享行为,而类是对象之间的关系模型。 技术上你可以只让一切对象的实例,并在你想要得到的行为中所需组的任何模块搭配,但是这将是一个贫穷的,杂乱无章,而无法读取设计。



Answer 4:

回答你的问题在很大程度上是情境。 蒸馏pubb的观察,选择主要取决于所考虑的领域驱动。

是的,ActiveRecord的应该已被列入,而不是由一个子类扩展。 另一个ORM - DataMapper的 -恰恰实现了!



Answer 5:

我喜欢安迪·加斯克尔的回答非常 - 只是想补充一点是,ActiveRecord的不应该使用继承,而是包括模块的行为(主要是持久性)添加到模型/班。 ActiveRecord的是简单地使用了错误的范例。

出于同样的原因,我非常喜欢MongoId在MongoMapper,因为它留下的开发人员使用继承造型的东西在问题领域有意义的方式的机会。

这是可悲的是,在Rails社区几乎没有人使用的“红宝石继承”使用它应该的方式 - 来定义类层次结构,而不只是添加行为。



Answer 6:

我理解的混入,最好的办法是虚拟类。 混入的“虚拟课堂”已经在一个类或模块的祖先链被注入。

当我们使用“包括”,并通过它的模块,它增加的模块,我们继承的类前右祖先链:

class Parent
end 

module M
end

class Child < Parent
  include M
end

Child.ancestors
 => [Child, M, Parent, Object ...

在Ruby中每个对象也有一个单独的类。 加入此singleton类的方法,可直接调用的对象,所以他们作为“类”的方法。 当我们使用一个对象上的“延伸”,并通过该对象的模块,我们添加的模块到单个类的对象的方法:

module M
  def m
    puts 'm'
  end
end

class Test
end

Test.extend M
Test.m

我们可以用singleton_class方法访问单例类:

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

Ruby提供用于模块的一些钩当它们混入类/模块。 included是由红宝石的钩方法每当你包括一些模块或类的模块,其被调用。 就像包括,有一个相关的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封装关注这种模式。 这里的改写,请使用ActiveSupport ::关注同一模块:

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


Answer 7:

现在,我正在考虑的template设计模式。 它只是不会感到右用的模块。



文章来源: ruby inheritance vs mixins