什么是mixin(作为概念)

时间:2023-01-15 18:42:42

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): 什么是mixin(作为概念)

如果你能根据下面的例子来解释你的答案,我将不胜感激。

5 个解决方案

#1


94  

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.

这种方法的问题在于,这种设计并没有提供任何直观的方法来将这些具体的类结合在一起。

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.

mix-in的想法是提供一些原始类,其中每个类都建模一个基本的正交概念,并能够将它们粘在一起,组成更复杂的类,只使用您想要的功能——有点像legos。原始类本身被用作构建块。这是可扩展的,因为稍后您可以向集合添加其他基本类,而不会影响现有类。

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.

回到c++,一种实现这一点的技术是使用模板和继承。这里的基本思想是通过模板参数将这些构建块连接在一起。然后把它们连在一起。通过typedef,形成包含所需功能的新类型。

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

以您的示例为例,假设我们想在上面添加redo功能。它是这样的:

#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() << '\n';  // 84
  mynum.undo();
  cout << mynum.get() << '\n';  // 42
  mynum.redo();
  cout << mynum.get() << '\n';  // back to 84
}

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

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

  • 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.
  • 我为第二个模板param添加了一个默认value_type,以减少它的使用。这样,您就不必每次都在一起输入 ,>
  • Instead of creating a new class that inherits from the pieces, a simple typedef is used.
  • 使用简单的typedef,而不是创建继承自片段的新类。

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.

作为旁注,您可能还会发现本文很有帮助。

#2


7  

A mixin is a class dessigned to provide functionality for another class, normally through a specified class which provides the basic features that the functionality needs. For example, consider your example:
The mixin in this case provides the functionality of undoing the set operation of a value class. This hability is based on the get/set functionality provided by a parametrized class (The Number class, in your example).

mixin是为另一个类提供功能的类,通常是通过指定的类提供功能所需的基本特性。例如,考虑您的示例:在本例中,mixin提供了取消值类的set操作的功能。这个hability是基于一个参数化类(在您的例子中是Number类)提供的get/set功能。

Another example (Extracted from "Mixin-based programming in C++"):

另一个例子(摘自“基于mixin的c++编程”):

template <class Graph>
class Counting: public Graph {
  int nodes_visited, edges_visited;
public:
  Counting() : nodes_visited(0), edges_visited(0), Graph() { }
  node succ_node (node v) {
    nodes_visited++;
    return Graph::succ_node(v);
  }
  edge succ_edge (edge e) {
    edges_visited++;
    return Graph::succ_edge(e);
  }
... 
};

In this example, the mixin provides the functionality of counting vertices, given a graph class that performs trasversal operations.

在本例中,mixin提供了计算顶点的功能,给出了一个执行遍历操作的图类。

Commonly, in C++ mixins are implemented through the CRTP idiom. This thread could be a good read about a mixin implementation in C++: What is C++ Mixin-Style?

通常,在c++中,混合是通过CRTP习惯用法实现的。这个线程可以很好地阅读c++中的mixin实现:c++的mixin风格是什么?

Here is an example of a mixin that takes advantage of the CRTP idiom (Thanks to @Simple):

下面是一个使用CRTP习语(感谢@Simple)的mixin示例:

#include <cassert>
#ifndef NDEBUG
#include <typeinfo>
#endif

class shape
{
public:
    shape* clone() const
    {
        shape* const p = do_clone();
        assert(p && "do_clone must not return a null pointer");
        assert(
            typeid(*p) == typeid(*this)
            && "do_clone must return a pointer to an object of the same type"
        );
        return p;
    }

private:
    virtual shape* do_clone() const = 0;
};

template<class D>
class cloneable_shape : public shape
{
private:
    virtual shape* do_clone() const
    {
        return new D(static_cast<D&>(*this));
    }
};

class triangle : public cloneable_shape<triangle>
{
};

class square : public cloneable_shape<square>
{
};

This mixin provides the functionality of heterogeneous copy to a set (hierarchy) of shape classes.

这个mixin为形状类的集合(层次结构)提供了异构复制的功能。

#3


5  

I like the answer from greatwolf, but would offer one point of caution.

我喜欢“大狼”的回答,但我要提醒你一点。

greatwolf stated, "The virtual functions really aren't necessary here because we know exactly what our composed class type is at compile-time." Unfortunately, you can run into some inconsistent behavior if you use your object polymorphically.

greatwolf说,“虚拟函数在这里并不是必需的,因为我们在编译时确切地知道我们的组合类类型是什么。”不幸的是,如果您以多态方式使用对象,您可能会遇到一些不一致的行为。

Let me tweak the main function from his example:

让我从他的示例中调整主函数:

int main()
{
  ReUndoableNumber mynum;
  Undoable<Number>* myUndoableNumPtr = &mynum;

  mynum.set(42);                // Uses ReUndoableNumber::set
  myUndoableNumPtr->set(84);    // Uses Undoable<Number>::set (ReUndoableNumber::after not set!)
  cout << mynum.get() << '\n';  // 84
  mynum.undo();
  cout << mynum.get() << '\n';  // 42
  mynum.redo();
  cout << mynum.get() << '\n';  // OOPS! Still 42!
}  

By making the "set" function virtual, the proper override will be called and the inconsistent behavior above will not occur.

通过使“set”函数为虚,将调用适当的覆盖,不会出现上述不一致的行为。

#4


4  

Mixins in C++ are expressed using the Curiously Recurring Template Pattern (CRTP). This post is an excellent breakdown of what they provide over other reuse techniques... compile-time polymorphism.

c++中的mixin是使用异常重复的模板模式(CRTP)来表示的。这篇文章是对他们提供的其他重用技术的优秀分类……编译时多态。

#5


0  

This works the same as an interface and maybe more so as an abstract, but interfaces are easier to get first time.

这与接口的工作原理相同,也可能更类似于抽象,但是接口更容易首次获得。

It addresses many issues but one I find in development that comes up a lot is external apis. imagine this.

它解决了很多问题,但我发现在开发过程中经常出现的一个问题是外部api。想象一下。

You have a database of users, that database has a certain way of getting access to its data. now imagine you have facebook, that also has a certain way of getting access to its data (api).

你有一个用户数据库,这个数据库有一种获取数据的方法。现在假设你有facebook,它也有某种访问其数据(api)的方式。

at any point your application may need to run using data from facebook or your database. so what you do is create an interface that says "anything that implements me is going to definitely have the following methods" now you can implement that interface into your application...

在任何时候,您的应用程序可能需要使用来自facebook或您的数据库的数据。所以你要做的就是创建一个接口,它说“任何实现我的东西肯定都有以下的方法”现在你可以在你的应用程序中实现那个接口……

because an interface promises that the implementing repositories will have the methods declared in them, you know that wherever or whenever you use that interface in your application, if you switch the data over, it's always going to have the methods you are defining and thus have data to work off of.

因为一个接口的承诺实现存储库将声明的方法,你知道哪里或当你在应用程序中使用该接口时,如果数据转过去,总是有你定义的方法,因此数据的工作。

There are many more layers to this pattern of working, but the essence is that it is good because data or other such persistant items become a big part of your application, and if they change without you knowing, your application can break :)

这种工作模式有很多层,但本质是它很好,因为数据或其他此类持久化项成为应用程序的重要组成部分,如果它们在不知道的情况下进行更改,则应用程序可以中断:)

Here's some pseudo code.

这里有一些伪代码。

interface IUserRepository
{
    User GetUser();
}

class DatabaseUserRepository : IUserRepository
{
    public User GetUser()
    {
        // Implement code for database
    }
}

class FacebookUserRepository : IUserRepository
{
    public User GetUser()
    {
        // Implement code for facebook
    }
}

class MyApplication
{
    private User user;

    MyApplication( IUserRepository repo )
    {
        user = repo;
    }
}

// your application can now trust that user declared in private scope to your application, will have access to a GetUser method, because if it isn't the interface will flag an error.

#1


94  

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.

这种方法的问题在于,这种设计并没有提供任何直观的方法来将这些具体的类结合在一起。

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.

mix-in的想法是提供一些原始类,其中每个类都建模一个基本的正交概念,并能够将它们粘在一起,组成更复杂的类,只使用您想要的功能——有点像legos。原始类本身被用作构建块。这是可扩展的,因为稍后您可以向集合添加其他基本类,而不会影响现有类。

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.

回到c++,一种实现这一点的技术是使用模板和继承。这里的基本思想是通过模板参数将这些构建块连接在一起。然后把它们连在一起。通过typedef,形成包含所需功能的新类型。

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

以您的示例为例,假设我们想在上面添加redo功能。它是这样的:

#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() << '\n';  // 84
  mynum.undo();
  cout << mynum.get() << '\n';  // 42
  mynum.redo();
  cout << mynum.get() << '\n';  // back to 84
}

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

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

  • 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.
  • 我为第二个模板param添加了一个默认value_type,以减少它的使用。这样,您就不必每次都在一起输入 ,>
  • Instead of creating a new class that inherits from the pieces, a simple typedef is used.
  • 使用简单的typedef,而不是创建继承自片段的新类。

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.

作为旁注,您可能还会发现本文很有帮助。

#2


7  

A mixin is a class dessigned to provide functionality for another class, normally through a specified class which provides the basic features that the functionality needs. For example, consider your example:
The mixin in this case provides the functionality of undoing the set operation of a value class. This hability is based on the get/set functionality provided by a parametrized class (The Number class, in your example).

mixin是为另一个类提供功能的类,通常是通过指定的类提供功能所需的基本特性。例如,考虑您的示例:在本例中,mixin提供了取消值类的set操作的功能。这个hability是基于一个参数化类(在您的例子中是Number类)提供的get/set功能。

Another example (Extracted from "Mixin-based programming in C++"):

另一个例子(摘自“基于mixin的c++编程”):

template <class Graph>
class Counting: public Graph {
  int nodes_visited, edges_visited;
public:
  Counting() : nodes_visited(0), edges_visited(0), Graph() { }
  node succ_node (node v) {
    nodes_visited++;
    return Graph::succ_node(v);
  }
  edge succ_edge (edge e) {
    edges_visited++;
    return Graph::succ_edge(e);
  }
... 
};

In this example, the mixin provides the functionality of counting vertices, given a graph class that performs trasversal operations.

在本例中,mixin提供了计算顶点的功能,给出了一个执行遍历操作的图类。

Commonly, in C++ mixins are implemented through the CRTP idiom. This thread could be a good read about a mixin implementation in C++: What is C++ Mixin-Style?

通常,在c++中,混合是通过CRTP习惯用法实现的。这个线程可以很好地阅读c++中的mixin实现:c++的mixin风格是什么?

Here is an example of a mixin that takes advantage of the CRTP idiom (Thanks to @Simple):

下面是一个使用CRTP习语(感谢@Simple)的mixin示例:

#include <cassert>
#ifndef NDEBUG
#include <typeinfo>
#endif

class shape
{
public:
    shape* clone() const
    {
        shape* const p = do_clone();
        assert(p && "do_clone must not return a null pointer");
        assert(
            typeid(*p) == typeid(*this)
            && "do_clone must return a pointer to an object of the same type"
        );
        return p;
    }

private:
    virtual shape* do_clone() const = 0;
};

template<class D>
class cloneable_shape : public shape
{
private:
    virtual shape* do_clone() const
    {
        return new D(static_cast<D&>(*this));
    }
};

class triangle : public cloneable_shape<triangle>
{
};

class square : public cloneable_shape<square>
{
};

This mixin provides the functionality of heterogeneous copy to a set (hierarchy) of shape classes.

这个mixin为形状类的集合(层次结构)提供了异构复制的功能。

#3


5  

I like the answer from greatwolf, but would offer one point of caution.

我喜欢“大狼”的回答,但我要提醒你一点。

greatwolf stated, "The virtual functions really aren't necessary here because we know exactly what our composed class type is at compile-time." Unfortunately, you can run into some inconsistent behavior if you use your object polymorphically.

greatwolf说,“虚拟函数在这里并不是必需的,因为我们在编译时确切地知道我们的组合类类型是什么。”不幸的是,如果您以多态方式使用对象,您可能会遇到一些不一致的行为。

Let me tweak the main function from his example:

让我从他的示例中调整主函数:

int main()
{
  ReUndoableNumber mynum;
  Undoable<Number>* myUndoableNumPtr = &mynum;

  mynum.set(42);                // Uses ReUndoableNumber::set
  myUndoableNumPtr->set(84);    // Uses Undoable<Number>::set (ReUndoableNumber::after not set!)
  cout << mynum.get() << '\n';  // 84
  mynum.undo();
  cout << mynum.get() << '\n';  // 42
  mynum.redo();
  cout << mynum.get() << '\n';  // OOPS! Still 42!
}  

By making the "set" function virtual, the proper override will be called and the inconsistent behavior above will not occur.

通过使“set”函数为虚,将调用适当的覆盖,不会出现上述不一致的行为。

#4


4  

Mixins in C++ are expressed using the Curiously Recurring Template Pattern (CRTP). This post is an excellent breakdown of what they provide over other reuse techniques... compile-time polymorphism.

c++中的mixin是使用异常重复的模板模式(CRTP)来表示的。这篇文章是对他们提供的其他重用技术的优秀分类……编译时多态。

#5


0  

This works the same as an interface and maybe more so as an abstract, but interfaces are easier to get first time.

这与接口的工作原理相同,也可能更类似于抽象,但是接口更容易首次获得。

It addresses many issues but one I find in development that comes up a lot is external apis. imagine this.

它解决了很多问题,但我发现在开发过程中经常出现的一个问题是外部api。想象一下。

You have a database of users, that database has a certain way of getting access to its data. now imagine you have facebook, that also has a certain way of getting access to its data (api).

你有一个用户数据库,这个数据库有一种获取数据的方法。现在假设你有facebook,它也有某种访问其数据(api)的方式。

at any point your application may need to run using data from facebook or your database. so what you do is create an interface that says "anything that implements me is going to definitely have the following methods" now you can implement that interface into your application...

在任何时候,您的应用程序可能需要使用来自facebook或您的数据库的数据。所以你要做的就是创建一个接口,它说“任何实现我的东西肯定都有以下的方法”现在你可以在你的应用程序中实现那个接口……

because an interface promises that the implementing repositories will have the methods declared in them, you know that wherever or whenever you use that interface in your application, if you switch the data over, it's always going to have the methods you are defining and thus have data to work off of.

因为一个接口的承诺实现存储库将声明的方法,你知道哪里或当你在应用程序中使用该接口时,如果数据转过去,总是有你定义的方法,因此数据的工作。

There are many more layers to this pattern of working, but the essence is that it is good because data or other such persistant items become a big part of your application, and if they change without you knowing, your application can break :)

这种工作模式有很多层,但本质是它很好,因为数据或其他此类持久化项成为应用程序的重要组成部分,如果它们在不知道的情况下进行更改,则应用程序可以中断:)

Here's some pseudo code.

这里有一些伪代码。

interface IUserRepository
{
    User GetUser();
}

class DatabaseUserRepository : IUserRepository
{
    public User GetUser()
    {
        // Implement code for database
    }
}

class FacebookUserRepository : IUserRepository
{
    public User GetUser()
    {
        // Implement code for facebook
    }
}

class MyApplication
{
    private User user;

    MyApplication( IUserRepository repo )
    {
        user = repo;
    }
}

// your application can now trust that user declared in private scope to your application, will have access to a GetUser method, because if it isn't the interface will flag an error.