Ich versuche, mich mit dem Mixin-Konzept vertraut zu machen, aber ich kann anscheinend nicht verstehen, was es ist. Ich sehe es so, dass es eine Möglichkeit ist, die Fähigkeiten einer Klasse durch Vererbung zu erweitern. Ich habe gelesen, dass die Leute sie als "abstrakte Unterklassen" bezeichnen. Kann jemand erklären warum?
Ich würde mich freuen, wenn Sie Ihre Antwort anhand des folgenden Beispiels erläutern würden (aus einer meiner Vorlesungs-Diashows):
Antworten:
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'; // 84 mynum.undo(); cout << mynum.get() << '\n'; // 42 mynum.redo(); cout << mynum.get() << '\n'; // back to 84 }
Sie werden feststellen, dass ich einige Änderungen an Ihrem Original vorgenommen habe:
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.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.
quelle
void Number::set(int)
undint Number::get() const
beide sollten seinvirtual
, das Mixin-Verhalten bei Verwendung einesNumber*
Zeigers zu erhalten.set
und dieget
virtuelle Klassevoid doubler(Number &n){ n.set(n.get()*2); }
beizubehalten, da Sie sie dann mit undoable und reundoable Klassen definieren und verwenden könnenNumber::value_type
bereits definiert es könnte (und sollte) auch verwendet werdenNumber::n
,Number::get
undNumber::set
.int
,std::string
,char
, etc. in C ++) ist Mixins selbst, nicht wahr?Ein Mixin ist eine Klasse, die so konzipiert ist, dass sie Funktionen für eine andere Klasse bereitstellt, normalerweise über eine bestimmte Klasse, die die grundlegenden Funktionen bereitstellt, die für die Funktionalität erforderlich sind. Betrachten Sie beispielsweise Ihr Beispiel:
Das Mixin bietet in diesem Fall die Funktionalität, die Set-Operation einer Wertklasse rückgängig zu machen. Diese Gewohnheit basiert auf der
get/set
Funktionalität einer parametrisierten Klasse (Number
in Ihrem Beispiel die Klasse).Ein weiteres Beispiel (Auszug aus " Mixin-basierte Programmierung 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); } ... };
In diesem Beispiel bietet das Mixin die Funktionalität zum Zählen von Scheitelpunkten bei einer Diagrammklasse, die Transversaloperationen ausführt.
In C ++ werden Mixins üblicherweise über das CRTP- Idiom implementiert . Dieser Thread könnte eine gute Lektüre über eine Mixin-Implementierung in C ++ sein: Was ist C ++ Mixin-Style?
Hier ist ein Beispiel für ein Mixin, das die CRTP-Sprache nutzt (Dank an @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> { };
Dieses Mixin bietet die Funktionalität einer heterogenen Kopie in eine Menge (Hierarchie) von Formklassen.
quelle
shape
Klasse zu definieren, aber das Ableiten voncloneable_shape
implementiert automatisch dieclone
Member-Funktion für Sie, sodass Sie sie nicht für jede Klasse, die Sie ableiten, selbst schreiben müssen.Ich mag die Antwort von Greatwolf, möchte aber einen Punkt der Vorsicht bieten.
greatwolf erklärte: "Die virtuellen Funktionen sind hier wirklich nicht notwendig, weil wir genau wissen, was unser zusammengesetzter Klassentyp zur Kompilierungszeit ist." Leider können Sie auf ein inkonsistentes Verhalten stoßen, wenn Sie Ihr Objekt polymorph verwenden.
Lassen Sie mich die Hauptfunktion anhand seines Beispiels optimieren:
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! }
Wenn Sie die Funktion "set" virtuell machen, wird die richtige Überschreibung aufgerufen, und das oben beschriebene inkonsistente Verhalten tritt nicht auf.
quelle
Mixins in C ++ werden mithilfe des CRTP (Curiously Recurring Template Pattern) ausgedrückt. Dieser Beitrag ist eine hervorragende Aufschlüsselung dessen, was sie gegenüber anderen Wiederverwendungstechniken bieten ... Polymorphismus zur Kompilierungszeit.
quelle
Dies funktioniert genauso wie eine Schnittstelle und vielleicht noch mehr als Zusammenfassung, aber Schnittstellen sind beim ersten Mal einfacher zu bekommen.
Es werden viele Probleme angesprochen, aber eines, das ich in der Entwicklung häufig finde, ist die externe API. Stell dir das vor.
Sie haben eine Datenbank mit Benutzern. Diese Datenbank hat eine bestimmte Möglichkeit, auf ihre Daten zuzugreifen. Stellen Sie sich jetzt vor, Sie haben Facebook, das auch eine bestimmte Möglichkeit hat, auf seine Daten (API) zuzugreifen.
Zu jedem Zeitpunkt muss Ihre Anwendung möglicherweise mit Daten von Facebook oder Ihrer Datenbank ausgeführt werden. Sie erstellen also eine Schnittstelle, die besagt, dass "alles, was mich implementiert, definitiv die folgenden Methoden hat". Jetzt können Sie diese Schnittstelle in Ihre Anwendung implementieren ...
Da eine Schnittstelle verspricht, dass in den implementierenden Repositorys die Methoden deklariert sind, wissen Sie, dass, wo oder wann immer Sie diese Schnittstelle in Ihrer Anwendung verwenden, wenn Sie die Daten umschalten, immer die Methoden vorhanden sind, die Sie definieren und somit haben Daten zu bearbeiten.
Es gibt viel mehr Ebenen in diesem Arbeitsmuster, aber das Wesentliche ist, dass es gut ist, weil Daten oder andere solche dauerhaften Elemente ein großer Teil Ihrer Anwendung werden und wenn sie sich ändern, ohne dass Sie es wissen, kann Ihre Anwendung brechen :)
Hier ist ein Pseudocode.
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.
quelle
Um das Konzept zu verstehen, vergessen Sie den Unterricht für einen Moment. Denken Sie an (am beliebtesten) JavaScript. Wo Objekte dynamische Arrays von Methoden und Eigenschaften sind. Mit ihrem Namen als Symbol oder als String-Literal aufrufbar. Wie würden Sie das in Standard C ++ in einem Jahr 2018 implementieren? Nicht leicht . Aber das ist der Kern des Konzepts. In JavaScript kann man hinzufügen und entfernen (auch als Mix-In bezeichnet), wann und was immer man möchte. Sehr wichtig: Keine Klassenvererbung.
Nun zu C ++. Standard C ++ hat alles was Sie brauchen, hilft hier nicht als Aussage. Natürlich werde ich keine Skriptsprache schreiben, um das Mix-In mit C ++ zu implementieren.
Ja, dies ist ein guter Artikel , aber nur zur Inspiration. CRTP ist kein Allheilmittel. Und auch der sogenannte akademische Ansatz basiert hier (im Wesentlichen) auf CRTP.
Bevor Sie diese Antwort herabstimmen, sollten Sie meinen Poc-Code auf der Zauberstabbox berücksichtigen :)
quelle