什么是 Mixin(作为一个概念)

2021-12-11 00:00:00 oop templates c++ mixins

我试图理解 Mixin 的概念,但我似乎无法理解它是什么.我认为它是一种通过使用继承来扩展类功能的方法.我读过人们将它们称为抽象子类".谁能解释一下为什么?

I'm trying to get my head around the Mixin concept but I can't seem to understand what it is. The way I see it is that it's a way to expand the capabilities of a class by using inheritance. I've read that people refer to them as "abstract subclasses". Can anyone explain why?

如果您能根据以下示例(来自我的演讲幻灯片之一)解释您的答案,我将不胜感激:

I'd appreciate if you'd explain your answer based on the following example (From one of my lecture slideshows):

推荐答案

在讨论混入是什么之前,描述它试图解决的问题很有用.假设您有一堆想法或概念要建模.它们可能以某种方式相关,但它们在大多数情况下是正交的――这意味着它们可以相互独立.现在,您可以通过继承对此进行建模,并使每个概念都派生自某个通用接口类.然后在实现该接口的派生类中提供具体方法.

Before going into what a mix-in is, it's useful to describe the problems it's trying to solve. Say you have a bunch of ideas or concepts you are trying to model. They may be related in some way but they are orthogonal for the most part -- meaning they can stand by themselves independently of each other. Now you might model this through inheritance and have each of those concepts derive from some common interface class. Then you provide concrete methods in the derived class that implements that interface.

这种方法的问题在于,这种设计没有提供任何清晰直观的方法来获取每个具体类并将它们组合在一起.

The problem with this approach is that this design does not offer any clear intuitive way to take each of those concrete classes and combine them together.

mix-ins 的想法是提供一堆原始类,其中每个类都为一个基本的正交概念建模,并且能够将它们粘在一起以组成更复杂的类,仅具有您想要的功能 - 有点像乐高积木.原始类本身旨在用作构建块.这是可扩展的,因为稍后您可以将其他原始类添加到集合中,而不会影响现有的类.

The idea with mix-ins is to provide a bunch of primitive classes, where each of them models a basic orthogonal concept, and be able to stick them together to compose more complex classes with just the functionality you want -- sort of like legos. The primitive classes themselves are meant to be used as building blocks. This is extensible since later on you can add other primitive classes to the collection without affecting the existing ones.

回到 C++,这样做的一种技术是使用模板和继承.这里的基本思想是通过模板参数将这些构建块连接在一起.然后你将它们链接在一起,例如.通过 typedef,形成一个包含你想要的功能的新类型.

Getting back to C++, a technique for doing this is using templates and inheritance. The basic idea here is you connect these building blocks together by providing them via the template parameter. You then chain them together, eg. via typedef, to form a new type containing the functionality you want.

以您的示例为例,假设我们要在顶部添加重做功能.下面是它的样子:

Taking your example, let say we want to add a redo functionality on top. Here's how it might look like:

#include <iostream>
using namespace std;

struct Number
{
  typedef int value_type;
  int n;
  void set(int v) { n = v; }
  int get() const { return n; }
};

template <typename BASE, typename T = typename BASE::value_type>
struct Undoable : public BASE
{
  typedef T value_type;
  T before;
  void set(T v) { before = BASE::get(); BASE::set(v); }
  void undo() { BASE::set(before); }
};

template <typename BASE, typename T = typename BASE::value_type>
struct Redoable : public BASE
{
  typedef T value_type;
  T after;
  void set(T v) { after = v; BASE::set(v); }
  void redo() { BASE::set(after); }
};

typedef Redoable< Undoable<Number> > ReUndoableNumber;

int main()
{
  ReUndoableNumber mynum;
  mynum.set(42); mynum.set(84);
  cout << mynum.get() << '
';  // 84
  mynum.undo();
  cout << mynum.get() << '
';  // 42
  mynum.redo();
  cout << mynum.get() << '
';  // back to 84
}

你会注意到我对你的原作做了一些改动:

You'll notice I made a few changes from your original:

  • 虚函数在这里真的不是必需的,因为我们在编译时确切地知道我们组合的类类型是什么.
  • 我为第二个模板参数添加了一个默认的 value_type 以使其使用不那么麻烦.这样您就不必每次将一个片段粘在一起时都继续输入 .
  • 使用一个简单的 typedef 而不是创建一个从片段继承的新类.
  • The virtual functions really aren't necessary here because we know exactly what our composed class type is at compile-time.
  • I've added a default value_type for the second template param to make its usage less cumbersome. This way you don't have to keep typing <foobar, int> everytime you stick a piece together.
  • Instead of creating a new class that inherits from the pieces, a simple typedef is used.

请注意,这只是一个简单的例子来说明混入的想法.所以它没有考虑极端情况和有趣的用法.例如,在没有设置数字的情况下执行 undo 可能不会像您预期的那样工作.

Note that this is meant to be a simple example to illustrate the mix-in idea. So it doesn't take into account corner cases and funny usages. For example, performing an undo without ever setting a number probably won't behave as you might expect.

作为旁注,您可能还会找到这篇文章 很有帮助.

As a sidenote, you might also find this article helpful.

相关文章