it-swarm.com.de

Was sind Mixins (als Konzept)

Ich versuche, mich mit dem Mixin-Konzept auseinanderzusetzen, aber ich kann nicht verstehen, was es ist ... Die Art, wie ich es sehe, ist die Möglichkeit, die Fähigkeiten einer Klasse durch die Verwendung von 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 erklären würden (aus einer meiner Vortrags-Diashows): A C++ Mixin Example

65
Shookie

Bevor Sie sich mit dem 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 können auf irgendeine Weise verwandt sein, aber sie sind zum größten Teil orthogonal - was bedeutet, dass sie unabhängig voneinander stehen können. Jetzt können Sie dies durch Vererbung modellieren und jedes dieser Konzepte von einer üblichen Schnittstellenklasse ableiten. Dann geben Sie konkrete Methoden in der abgeleiteten Klasse an, die diese Schnittstelle implementiert.

Das Problem bei diesem Ansatz ist, dass dieses Design keine eindeutige intuitive Möglichkeit bietet, jede dieser konkreten Klassen zu nehmen und sie zu kombinieren. 

Die Idee mit Mix-Ins besteht darin, eine Reihe primitiver Klassen bereitzustellen, bei denen jede ein grundlegendes orthogonales Konzept modelliert und in der Lage ist, sie zusammenzufügen, um komplexere Klassen mit der gewünschten Funktionalität zusammenzustellen - wie Legos. Die primitiven Klassen selbst sollen als Bausteine ​​verwendet werden. Dies ist erweiterbar, da Sie später weitere primitive Klassen zur Sammlung hinzufügen können, ohne die vorhandenen zu beeinflussen.

Um wieder auf C++ zuzugreifen, verwenden Sie dazu Vorlagen und Vererbung. Die Grundidee hierbei ist, dass Sie diese Bausteine ​​miteinander verbinden, indem Sie sie über den Parameter template bereitstellen. Sie ketten sie dann zusammen, z. über typedef, um einen neuen Typ mit der gewünschten Funktionalität zu bilden.

Nehmen wir zum Beispiel an, wir wollen 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 notwendig, da wir genau wissen, was unser zusammengesetzter Klassentyp zur Kompilierzeit ist.
  • Ich habe einen Standard value_type für das zweite Template param hinzugefügt, um dessen Verwendung weniger umständlich zu gestalten. Auf diese Weise müssen Sie nicht jedes Mal <foobar, int> eingeben, wenn Sie ein Stück zusammenhalten.
  • Anstatt eine neue Klasse zu erstellen, die von den Stücken erbt, wird eine einfache typedef verwendet.

Beachten Sie, dass dies ein einfaches Beispiel sein soll, um die Einmischidee zu veranschaulichen. Daher werden Eckfälle und witzige Verwendungen nicht berücksichtigt. Wenn Sie beispielsweise eine undo ausführen, ohne eine Zahl festzulegen, wird sich das wahrscheinlich nicht wie erwartet verhalten.

Als Randbemerkung können Sie auch diesen Artikel / hilfreich finden.

107
greatwolf

Ein Mixin ist eine Klasse, die dazu dient, Funktionalität für eine andere Klasse bereitzustellen, normalerweise durch eine angegebene Klasse, die die grundlegenden Funktionen bereitstellt, die die Funktionalität benötigt. Betrachten Sie zum Beispiel Ihr Beispiel:
Das Mixin bietet in diesem Fall die Funktionalität, den gesetzten Vorgang einer Werteklasse rückgängig zu machen. Diese Stabilität basiert auf der get/set-Funktionalität, die von einer parametrisierten Klasse bereitgestellt wird (in Ihrem Beispiel die Number-Klasse).

Ein anderes Beispiel (Auszug aus "Mixin-basierter 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 von counting vertices, wenn eine Diagrammklasse angegeben ist, die transversale Operationen 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 eines Mixins, das das CRTP-Idiom nutzt (Danke 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 von heterogener Kopie zu einer Menge (Hierarchie) von Formklassen.

7
Manu343726

Ich mag die Antwort von Greatwolf, würde 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 Kompilierzeit ist." Leider kann es zu inkonsistentem Verhalten kommen, 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 "Set" -Funktion virtuell machen, wird die richtige Überschreibung aufgerufen, und das oben beschriebene inkonsistente Verhalten tritt nicht auf.

5
Ken

Mixins in C++ werden mit Curiously Recurring Template Pattern (CRTP) ausgedrückt. Dieser Beitrag ist eine hervorragende Aufschlüsselung dessen, was sie im Vergleich zu anderen Wiederverwendungstechniken bieten ... zum Kompilierzeit-Polymorphismus.

4
Jesse Pepper

Dies funktioniert genauso wie eine Schnittstelle und vielleicht mehr als eine abstrakte, aber Schnittstellen sind beim ersten Mal einfacher zu bekommen. 

Es befasst sich mit vielen Problemen, aber eine, die ich in der Entwicklung finde, ist viel von außen. Stell dir das vor. 

Sie haben eine Datenbank von 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, wo und wann immer Sie diese Schnittstelle in Ihrer Anwendung verwenden, wenn Sie die Daten umschalten, werden immer die Methoden vorhanden sein, die Sie definieren Daten, um davon zu arbeiten. 

Es gibt viele weitere Ebenen dieses Arbeitsmusters, aber das Wesentliche ist, dass es gut ist, weil Daten oder andere solche persistenten Elemente zu einem großen Teil Ihrer Anwendung werden. Wenn sie sich ändern, ohne dass Sie es merken, kann Ihre Anwendung brechen.

Hier ist ein Pseudo-Code.

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.
0
Jimmyt1988

Um das Konzept zu verstehen, vergessen Sie den Unterricht für einen Moment. Denken Sie an (das beliebteste) JavaScript. Objekte sind dynamische Arrays von Methoden und Eigenschaften. Kann mit ihrem Namen als Symbol oder als String-Literal aufgerufen werden. Wie würden Sie das in einem Jahr 2018 in Standard C++ implementieren? Nicht einfach. Aber das ist der Kern des Konzepts. In JavaScript kann man hinzufügen und entfernen (aka mix-in), wann immer und was auch immer man möchte. Sehr wichtig: Keine Klassenvererbung.

Nun zu C++. Standard C++ hat alles was Sie brauchen, als Aussage hier hilft es nicht. Offensichtlich 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 ist hier , (im Wesentlichen) CRTP-basiert.

Vor dem Abstimmen dieser Antwort erwägen Sie vielleicht meine p.o.c. Code auf Zauberstab :)

0
user5560811