ruby 继承与 mixins

发布于 2024-08-01 18:34:22 字数 237 浏览 5 评论 0原文

在 Ruby 中,由于您可以包含多个 mixins,但只能扩展一个类,因此看起来 mixins 比继承更受青睐。

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

我只能想到你想要一个类的一个原因,那就是你是否需要实例化该类。 然而,对于 ActiveRecord::Base,您永远不会直接实例化它。 那么它不应该是一个模块吗?

In Ruby, since you can include multiple mixins but only extend one class, it seems like mixins would be preferred over inheritance.

My question: if you're writing code which must be extended/included to be useful, why would you ever make it a class? Or put another way, why wouldn't you always make it a module?

I can only think of one reason why you'd want a class, and that is if you need to instantiate the class. In the case of ActiveRecord::Base, however, you never instantiate it directly. So shouldn't it have been a module instead?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

作业与我同在 2024-08-08 18:34:22

我的看法:模块用于共享行为,而类用于建模对象之间的关系。 从技术上讲,您可以将所有内容都作为 Object 的实例,并混合到您想要获得所需行为集的任何模块中,但这将是一个糟糕的、随意的且相当不可读的设计。

My take: Modules are for sharing behavior, while classes are for modeling relationships between objects. You technically could just make everything an instance of Object and mix in whatever modules you want to get the desired set of behaviors, but that would be a poor, haphazard and rather unreadable design.

时光病人 2024-08-08 18:34:22

你的问题的答案很大程度上取决于上下文。 总结 pubb 的观察结果,选择主要是由所考虑的领域驱动的。

是的,ActiveRecord 应该包含在子类中而不是扩展它。 另一个 ORM - datamapper - 精确地实现了这一点!

The answer to your question is largely contextual. Distilling pubb's observation, the choice is primarily driven by the domain under consideration.

And yes, ActiveRecord should have been included rather than extended by a subclass. Another ORM - datamapper - precisely achieves that!

无畏 2024-08-08 18:34:22

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

出于同样的原因,我非常喜欢 MongoId 而不是 MongoMapper,因为它让开发人员有机会使用继承来对问题领域中有意义的内容进行建模。

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

I like Andy Gaskell's answer very much - just wanted to add that yes, ActiveRecord should not use inheritance, but rather include a module to add the behavior (mostly persistence) to a model/class. ActiveRecord is simply using the wrong paradigm.

For the same reason, I very much like MongoId over MongoMapper, because it leaves the developer the chance to use inheritance as a way of modelling something meaningful in the problem domain.

It's sad that pretty much nobody in the Rails community is using "Ruby inheritance" the way it's supposed to be used - to define class hierarchies, not just to add behavior.

贪了杯 2024-08-08 18:34:22

我理解 mixin 的最好方法是作为虚拟类。 Mixin 是已注入类或模块的祖先链中的“虚拟类”。

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

class Parent
end 

module M
end

class Child < Parent
  include M
end

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

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

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 是 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

如您所见,这个单一模块添加实例方法、“类”方法,并直接作用于目标类(在本例中调用 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

The best way I understand mixins are as virtual classes. Mixins are "virtual classes" that have been injected in a class's or module's ancestor chain.

When we use "include" and pass it a module, it adds the module to the ancestor chain right before the class that we are inheriting from:

class Parent
end 

module M
end

class Child < Parent
  include M
end

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

Every object in Ruby also has a singleton class. Methods added to this singleton class can be directly called on the object and so they act as "class" methods. When we use "extend" on an object and pass the object a module, we are adding the methods of the module to the singleton class of the object:

module M
  def m
    puts 'm'
  end
end

class Test
end

Test.extend M
Test.m

We can access the singleton class with the singleton_class method:

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

Ruby provides some hooks for modules when they are being mixed into classes/modules. included is a hook method provided by Ruby which gets called whenever you include a module in some module or class. Just like included, there is an associated extended hook for extend. It will be called when a module is extended by another module or class.

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

This creates an interesting pattern that developers could use:

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

As you can see, this single module is adding instance methods, "class" methods, and acting directly on the target class (calling a_class_method() in this case).

ActiveSupport::Concern encapsulates this pattern. Here's the same module rewritten to use 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
醉殇 2024-08-08 18:34:22

我认为 mixin 是一个好主意,但是这里还有一个没有人提到的问题:命名空间冲突。 考虑一下:

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 中,事实证明是后者,模块 B,因为您将其包含在模块 A 之后。 现在,很容易避免这个问题:确保模块 A 和模块 B 的所有常量和方法都位于不太可能的命名空间中。 问题在于,当发生冲突时,编译器根本不会警告您。

我认为这种行为不会扩展到大型程序员团队——您不应该假设实现类 C 的人知道范围内的每个名称。 Ruby 甚至允许您重写不同类型的常量或方法。 我不确定这是否可以被视为正确的行为。

I think mixins are a great idea, but there's another problem here that nobody has mentioned: namespace collisions. Consider:

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

Which one wins? In Ruby, it turns out the be the latter, module B, because you included it after module A. Now, it's easy to avoid this problem: make sure all of module A and module B's constants and methods are in unlikely namespaces. The problem is that the compiler doesn't warn you at all when collisions happen.

I argue that this behavior does not scale to large teams of programmers-- you shouldn't assume that the person implementing class C knows about every name in scope. Ruby will even let you override a constant or method of a different type. I'm not sure that could ever be considered correct behavior.

情愿 2024-08-08 18:34:22

刚刚The Well-Grounded Rubyist中读到了这个主题(顺便说一下,这是一本很棒的书)。 作者的解释比我做得更好,所以我引用他的话:


没有任何单一的规则或公式总是能产生正确的设计。 但保留一个是有用的
当您做出类与模块决策时,请记住以下几点:

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

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

在一个示例中总结这些规则,以下是您不应该做的事情:

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

相反,您应该这样做:

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

第二个版本对实体和属性进行建模更加整洁。 卡车
源自车辆(这是有道理的),而自推进是车辆的一个特征(至少,我们在这个世界模型中关心的所有车辆)——由于卡车是后代而传递给卡车的特征,或者专门
形式,车辆。

I just read about this topic in The Well-Grounded Rubyist (great book, by the way). The author does a better job of explaining than I would so I'll quote him:


No single rule or formula always results in the right design. But it’s useful to keep a
couple of considerations in mind when you’re making class-versus-module decisions:

  • Modules don’t have instances. It follows that entities or things are generally best
    modeled in classes, and characteristics or properties of entities or things are
    best encapsulated in modules. Correspondingly, as noted in section 4.1.1, class
    names tend to be nouns, whereas module names are often adjectives (Stack
    versus Stacklike).

  • A class can have only one superclass, but it can mix in as many modules as it wants. If
    you’re using inheritance, give priority to creating a sensible superclass/subclass
    relationship. Don’t use up a class’s one and only superclass relationship to
    endow the class with what might turn out to be just one of several sets of characteristics.

Summing up these rules in one example, here is what you should not do:

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

Rather, you should do this:

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

The second version models the entities and properties much more neatly. Truck
descends from Vehicle (which makes sense), whereas SelfPropelling is a characteristic of vehicles (at least, all those we care about in this model of the world)—a characteristic that is passed on to trucks by virtue of Truck being a descendant, or specialized
form, of Vehicle.

人生戏 2024-08-08 18:34:22

现在,我正在考虑 template 设计模式。 使用模块感觉不太对劲。

Right now, I'm thinking about the template design pattern. It just wouldn't feel right with a module.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文