【问题标题】:What are Mixins (as a concept)什么是 Mixin(作为一个概念)
【发布时间】:2013-09-17 09:03:00
【问题描述】:

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

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

【问题讨论】:

  • 一个大量使用 mixins 的框架是用于 Java Web 应用程序的 Apache Tapestry。阅读文档并查看 Tapestry 中的一些示例,也许您将能够看到与您在 C++ 示例中看到的内容的相似之处/模式。这是一个链接:tapestry.apache.org/component-mixins.html
  • 我完全以为你在谈论 Ruby 只是看标题...

标签: c++ oop templates mixins


【解决方案1】:

在讨论什么是混合之前,先描述一下它试图解决的问题是很有用的。假设您有一堆要建模的想法或概念。它们可能以某种方式相关,但它们在很大程度上是正交的——这意味着它们可以彼此独立地独立存在。现在,您可以通过继承对此进行建模,并让这些概念中的每一个都派生自某个公共接口类。然后在实现该接口的派生类中提供具体方法。

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

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

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

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

#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
}

您会注意到我对您的原始版本做了一些更改:

  • 这里实际上不需要虚函数,因为我们在编译时就知道我们的组合类类型是什么。
  • 我为第二个模板参数添加了一个默认的value_type,以减少它的使用麻烦。这样您就不必每次将一块拼凑在一起时都输入&lt;foobar, int&gt;
  • 使用简单的typedef,而不是创建从片段继承的新类。

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

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

【讨论】:

  • 这个例子实际上非常好,我真的读了它并且惊讶地发现它很有意义哈哈。干得好伙计。谢谢。
  • 读者注意,void Number::set(int)int Number::get() const 都应该是 virtual 以在使用 Number* 指针时获得混合行为。
  • 保留基类 setget 虚拟是有意义的,因为您可以定义 void doubler(Number &amp;n){ n.set(n.get()*2); } 并能够使用可撤销和可撤销的类
  • 一个建议:当Number::value_type 已经定义时,它可以(并且应该)也用于Number::nNumber::getNumber::set
  • 根据我对这篇文章推理的理解,似乎可以使用相同的推理来表示任何编程语言的本机/原始类型(例如intstd::string , char 等 C++ 中的) 本身就是 mixins,对吧?
【解决方案2】:

mixin 是一个类,旨在为另一个类提供功能,通常通过一个指定的类来提供功能所需的基本特性。例如,考虑您的示例:
在这种情况下,mixin 提供了撤消值类的设置操作的功能。这种能力基于参数化类(在您的示例中为 Number 类)提供的 get/set 功能。

另一个例子(摘自"Mixin-based programming in 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);
  }
... 
};

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

通常,在 C++ 中,mixin 是通过 CRTP 成语实现的。这个线程可以很好地阅读 C++ 中的 mixin 实现:What is C++ Mixin-Style?

这是一个利用 CRTP 习惯用法的 mixin 示例(感谢 @Simple):

#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>
{
};

这个 mixin 提供了异构复制到一组形状类(层次结构)的功能。

【讨论】:

  • 这个例子根本不是CRTP。
  • 但我喜欢您提到 CRTP 的评论。因为 mixin 在 C++ 中是用 CRTP 来近似的。
  • 任何人都有一个很好的现实生活例子,很容易理解,我认为它会很好地完成这个答案和一组 cmets。
  • @Simple 谢谢,我已将其添加到答案中。您的示例为类提供了“可克隆”的能力,对吗? 异构副本是正确的术语吗?我不确定。
  • @Manu343726 它允许您定义与shape类具有is-a关系的类,但从cloneable_shape派生自动实现clone成员函数为你,所以你不必为你派生的每个类都自己写。
【解决方案3】:

我喜欢 Greatwolf 的回答,但要注意一点。

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

让我从他的例子中调整主要功能:

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!
}  

通过将“set”函数设为虚拟,将调用适当的覆盖,并且不会发生上述不一致的行为。

【讨论】:

    【解决方案4】:

    C++ 中的 Mixin 使用 Curiously Recurring Template Pattern (CRTP) 表示。 This post 是他们提供的优于其他重用技术的一个很好的细分......编译时多态性。

    【讨论】:

      【解决方案5】:

      为了理解这个概念,暂时忘记类。想想(最流行的)JavaScript。对象是方法和属性的动态数组。可以通过它们的名称作为符号或字符串文字来调用。您将如何在 2018 年用标准 C++ 实现它? 不容易。但这就是这个概念的核心。在 JavaScript 中,可以随时随地添加和删除(也称为混合)。非常重要:没有类继承。

      现在进入 C++。标准 C++ 有你需要的一切,在这里作为陈述没有帮助。显然,我不会为了使用 C++ 实现混合而编写脚本语言。

      是的,this is a good article,但仅供参考。 CRTP 不是灵丹妙药。还有所谓的学术方法是here,也(本质上)基于CRTP。

      在否决这个答案之前,不妨考虑一下我的p.o.c. code on wand box :)

      【讨论】:

        【解决方案6】:

        这与接口的工作方式相同,并且可能更像抽象,但接口更容易第一次获得。

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

        您有一个用户数据库,该数据库具有访问其数据的特定方式。 现在想象一下你有 facebook,它也有某种方式来访问它的数据 (api)。

        在任何时候,您的应用程序都可能需要使用来自 facebook 或您的数据库的数据来运行。所以你要做的是创建一个接口,上面写着“任何实现我的东西肯定会有以下方法”现在你可以在你的应用程序中实现这个接口......

        因为接口承诺实现存储库将具有在其中声明的方法,所以您知道无论何时何地您在应用程序中使用该接口,如果您切换数据,它总是会有您定义的方法因此有数据可以处理。

        这种工作模式还有很多层,但本质是它很好,因为数据或其他此类持久性项目成为您应用程序的重要组成部分,如果它们在您不知情的情况下发生变化,您的应用程序可能会崩溃: )

        这是一些伪代码。

        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.
        

        【讨论】:

        • 您的代码看起来像 java,而不像 c++。另外,mixin 与普通的抽象类有什么不同?
        • 嗯,我做 C#、PHP 和 C++...我实际上说它们与 abstract 非常相似,但我发现很难解释它们的用途...考虑到我还没有使用它们许许多多 :)。每当我尝试提出一个好的继承示例时,我都会让自己哭泣。代码是为了用冗长的语法更容易地演示,但它不是真正的代码,我说的是伪代码 :) 我希望有人能更清楚地回答它,但我个人确实喜欢更多冗长的答案,我发现它们更有帮助,也是一个很好的现实生活例子^_^
        • 好吧,这让我很奇怪。看完后,我在网上搜索,找不到一个像样的解释。我仍然不明白什么是混合:/
        • 我知道您的意图是好的,但您的解释使情况比现在更加模糊。更不用说您错过了“in C++”部分。
        猜你喜欢
        • 2014-10-20
        • 1970-01-01
        • 2010-11-24
        • 1970-01-01
        • 2021-11-24
        • 1970-01-01
        • 2011-01-15
        • 2011-06-18
        相关资源
        最近更新 更多