Bevor Sie sich mit einem Mix-In befassen, sollten Sie die Probleme beschreiben, die es zu lösen versucht. Angenommen, Sie haben eine Reihe von Ideen oder Konzepten, die Sie modellieren möchten. Sie mögen in irgendeiner Weise verwandt sein, sind aber größtenteils orthogonal - was bedeutet, dass sie unabhängig voneinander für sich selbst stehen können. Jetzt können Sie dies durch Vererbung modellieren und jedes dieser Konzepte von einer gemeinsamen Schnittstellenklasse ableiten lassen. Anschließend stellen Sie in der abgeleiteten Klasse konkrete Methoden bereit, die diese Schnittstelle implementieren.
Das Problem bei diesem Ansatz ist, dass dieses Design keine klare intuitive Möglichkeit bietet, jede dieser konkreten Klassen zu nehmen und miteinander zu kombinieren.
Die Idee bei Mix-Ins ist es, eine Reihe primitiver Klassen bereitzustellen, von denen jede ein orthogonales Grundkonzept modelliert, und sie zusammenzufügen, um komplexere Klassen mit genau der gewünschten Funktionalität zu erstellen - ähnlich wie Legos. Die primitiven Klassen selbst sollen als Bausteine verwendet werden. Dies ist erweiterbar, da Sie später der Sammlung weitere primitive Klassen hinzufügen können, ohne die vorhandenen zu beeinflussen.
Wenn Sie zu C ++ zurückkehren, verwenden Sie dazu Vorlagen und Vererbung. Die Grundidee hier ist, dass Sie diese Bausteine miteinander verbinden, indem Sie sie über den Vorlagenparameter bereitstellen. Sie verketten sie dann miteinander, z. via typedef
, um einen neuen Typ zu bilden, der die gewünschte Funktionalität enthält.
Nehmen wir an, wir möchten zusätzlich eine Wiederherstellungsfunktion hinzufügen. So könnte es aussehen:
#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';
mynum.undo();
cout << mynum.get() << '\n';
mynum.redo();
cout << mynum.get() << '\n';
}
Sie werden feststellen, dass ich einige Änderungen an Ihrem Original vorgenommen habe:
- Die virtuellen Funktionen sind hier wirklich nicht erforderlich, da wir genau wissen, was unser zusammengesetzter Klassentyp zur Kompilierungszeit ist.
- Ich habe einen Standard
value_type
für den zweiten Vorlagenparameter hinzugefügt, um dessen Verwendung weniger umständlich zu machen. Auf diese Weise müssen Sie nicht <foobar, int>
jedes Mal tippen , wenn Sie ein Stück zusammenkleben.
- Anstatt eine neue Klasse zu erstellen, die von den Teilen erbt, wird eine einfache
typedef
verwendet.
Beachten Sie, dass dies ein einfaches Beispiel sein soll, um die Mix-In-Idee zu veranschaulichen. Eckfälle und lustige Verwendungen werden also nicht berücksichtigt. Wenn Sie beispielsweise eine durchführen, undo
ohne jemals eine Zahl festzulegen, verhält es sich wahrscheinlich nicht so, wie Sie es erwarten.
Als Nebenbemerkung finden Sie diesen Artikel möglicherweise auch hilfreich.