什么是混合(作为一个概念)
我试图让我的头在Mixin的概念,但我似乎无法理解它是什么。 我看到的方式是通过inheritance来扩展类的function。 我读过,人们称他们为“抽象的小类”。 谁能解释为什么?
如果你能根据下面的例子来解释你的答案,我将不胜感激(来自我的演讲幻灯片演示):
在进入混合模式之前,先描述它试图解决的问题是很有用的。 假设您有一堆想要build模的想法或概念。 它们可能在某种程度上是相关的,但是它们大部分是正交的 – 意味着它们可以彼此独立地站立。 现在,您可以通过inheritance对其进行build模,并使每个概念都从一些常见的接口类派生。 然后在派生类中提供实现该接口的具体方法。
这种方法的问题在于,这种devise并没有提供任何明确的直观方法来把每一个具体的类组合在一起。
混合插入的想法是提供一堆原始类,其中每个类都build立了一个基本的正交概念,并且能够将它们粘在一起,只用你想要的function来组合更复杂的类 – 有点像legos。 原始类本身是用作构build块的。 这是可扩展的,因为以后您可以将其他原始类添加到集合中,而不会影响现有类。
回到C ++,一个这样做的技巧是使用模板和inheritance。 这里的基本思想是通过模板参数提供这些构build块。 然后你把它们连在一起,例如。 通过typedef
,形成一个包含你想要的function的新types。
以您为例,假设我们要在顶部添加重做function。 以下是它的样子:
#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 }
你会注意到我做了一些你原来的改变:
- 虚函数在这里并不是必须的,因为我们确切知道在编译时我们组合的类的types。
- 我已经为第二个模板参数添加了一个默认的
value_type
,使其用法更简单。 这样你就不用每次把一块拼贴在一起的时候继续input<foobar, int>
。 - 而不是创build一个新的类从inheritance,一个简单的
typedef
被使用。
请注意,这是一个简单的例子来说明混合的想法。 所以它不考虑angular落案例和有趣的用法。 例如,执行undo
而不设置数字可能不会像你所期望的那样行事。
作为一个旁注,你也可能会发现这篇文章有帮助。
mixin是一个为其他类提供function的类,通常通过指定的类提供function所需的基本function。 例如,考虑你的例子:
在这种情况下,mixin提供了撤销值类的设置操作的function。 这种可变性基于参数化类(在你的例子中是Number
类)提供的get/set
function。
另一个例子(摘自“ 基于混合编程的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提供了计算顶点的function,给定一个执行trasversal操作的图类。
通常,在C ++中,mixin是通过CRTP惯用语来实现的。 这个线程可能是一个很好的阅读关于在C ++混合实现: 什么是C + +混合风格?
这是一个混合使用CRTP习惯用法的例子(感谢@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提供异构复制到一个形状类的集合(层次结构)的function。
C ++中的Mixin使用奇怪的循环模板模式 (CRTP)来表示。 这篇文章是他们提供的优于其他重用技术的细分…编译时多态性。
我喜欢伟大的狼人的回答,但会提供一点警惕。
伟大的狼说:“这里的虚拟函数并不是必须的,因为我们确切地知道我们编写的类的types是在编译时。” 不幸的是,如果你多态地使用你的对象,你可能会遇到一些不一致的行为。
让我从他的例子中调整主要function:
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”函数为虚拟,将调用适当的覆盖,并且不会发生上述不一致的行为。
这与接口相同,也许更像抽象,但接口更容易获得第一次。
它解决了许多问题,但我发现在开发中出现了很多问题是外部apis。 想象一下。
你有一个用户数据库,那个数据库有一定的访问数据的方法。 现在想象你有Facebook,也有一定的方法来访问其数据(api)。
在任何时候,您的应用程序可能需要使用来自Facebook或您的数据库的数据运行。 所以你要做的就是创build一个接口,说“实现我的任何东西肯定会有以下方法”,现在你可以在你的应用程序中实现这个接口了。
因为一个接口承诺实现的存储库将拥有在其中声明的方法,所以你知道在你的应用程序中无论何时何地使用这个接口,如果你切换数据,总是会有你定义的方法,数据的工作。
这种工作模式还有更多的层次,但其实质是好的,因为数据或其他这样的持久性项目成为您的应用程序的重要组成部分,如果它们在您不知情的情况下发生变化,您的应用程序可能会中断:)
这是一些伪代码。
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.