Was sind Mixins (als Konzept)


74

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): Ein C ++ Mixin Beispiel


Ein Framework, das Mixins stark nutzt, ist Apache Tapestry für Java-Webanwendungen. Lesen Sie die Dokumentation und sehen Sie sich einige Beispiele in Tapestry an. Vielleicht können Sie die Parallelen / Muster zu dem sehen, was Sie im C ++ - Beispiel sehen. Hier ist ein Link: tapestry.apache.org/component-mixins.html
David Fleeman

Ich dachte total, du sprichst nur durch einen Blick auf den Titel über Ruby ...
Texasbruce

Antworten:


130

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:

  • Die virtuellen Funktionen sind hier wirklich nicht erforderlich, da wir genau wissen, was unser zusammengesetzter Klassentyp zur Kompilierungszeit ist.
  • Ich habe einen Standard value_typefü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 typedefverwendet.

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, undoohne 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.


7
Dieses Beispiel ist eigentlich sehr gut, ich habe es tatsächlich gelesen und war überrascht, dass es viel Sinn machte, lolol. Gut gemacht, Kumpel. Vielen Dank.
Jimmyt1988

12
Ein Hinweis für Leser, void Number::set(int)und int Number::get() constbeide sollten sein virtual, das Mixin-Verhalten bei Verwendung eines Number*Zeigers zu erhalten.
Prashant Kumar

2
Es ist sinnvoll, die Basisklasse setund die getvirtuelle Klasse void doubler(Number &n){ n.set(n.get()*2); }beizubehalten, da Sie sie dann mit undoable und reundoable Klassen definieren und verwenden können
Roddy

7
Ein Vorschlag: wenn Number::value_typebereits definiert es könnte (und sollte) auch verwendet werden Number::n, Number::getund Number::set.
Miroslaw Opoka

Basierend auf meinem Verständnis der Argumentation in diesem Beitrag, so scheint es die gleiche Argumentationslinie verwendet werden kann , zu sagen , dass der nativ / Urtyp von einem beliebigen Programmiersprache (zB int, std::string, char, etc. in C ++) ist Mixins selbst, nicht wahr?
code_dredd

7

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/setFunktionalität einer parametrisierten Klasse ( Numberin 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.


7
Das Beispiel ist überhaupt nicht CRTP.
Lapk

1
Aber ich mochte Ihren Kommentar zu CRTP. Weil Mixins in C ++ mit CRTP angenähert werden.
Runde

1
Jeder hat ein schönes Beispiel aus dem wirklichen Leben, das leicht zu verstehen ist. Es würde diese Antwort und die Reihe von Kommentaren gut abschließen, denke ich.
Jimmyt1988

1
@ Einfach danke, das habe ich der Antwort hinzugefügt. Ihr Beispiel bietet die Gewohnheit, für eine Klasse "klonbar" zu sein, oder? Heterogene Kopie ist der richtige Begriff? Ich bin mir nicht sicher.
Manu343726

1
@ Manu343726 Es ermöglicht Ihnen, Klassen mit einer is-a- Beziehung zur shapeKlasse zu definieren, aber das Ableiten von cloneable_shapeimplementiert automatisch die cloneMember-Funktion für Sie, sodass Sie sie nicht für jede Klasse, die Sie ableiten, selbst schreiben müssen.
Einfach

6

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.



0

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.

4
Ihr Code sieht aus wie Java und nicht wie C ++. Wie unterscheiden sich Mixins von normalen abstrakten Klassen?
BЈовић

na ja, ich mache C #, PHP und C ++ ... Ich habe tatsächlich gesagt, dass sie abstrakt sehr ähnlich sind, aber ich finde es schwieriger, ihre Verwendung zu erklären ... wenn man bedenkt, dass ich sie noch nicht viele, viele verwendet habe :). Immer wenn ich versuche, ein gutes Erbschaftsbeispiel zu finden, bringe ich mich zum Weinen. Der Code sollte mit wortreicher Syntax einfacher zu demonstrieren sein, aber es ist kein echter Code, Pseudo, sagte ich :) Ich hoffe, jemand antwortet viel klarer, aber ich persönlich mag wortreichere Antworten, ich finde sie hilfreicher und ein gutes Beispiel aus dem wirklichen Leben ^ _ ^
Jimmyt1988

Ok, ich habe mich gefragt. Nachdem ich dies gelesen hatte, suchte ich im Internet und konnte keine anständige Erklärung finden. Ich verstehe es immer noch nicht, was Mixins sind: /
BЈовић

Ich verstehe, dass Sie gute Absichten haben, aber Ihre Erklärung macht das Bild verschwommener als es bereits war. Ganz zu schweigen von der Tatsache, dass Sie den Teil "in C ++" verpasst haben.
Ashrasmun

0

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 :)

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.