web-dev-qa-db-fra.com

Que sont les Mixins (en tant que concept)

J'essaie de comprendre le concept Mixin mais je n'arrive pas à comprendre en quoi il consiste ..__Je pense que c'est un moyen d'étendre les capacités d'une classe en utilisant l'héritage .J'ai lu que les gens les appellent "sous-classes abstraites". Quelqu'un peut-il expliquer pourquoi?

Je vous serais reconnaissant de bien vouloir expliquer votre réponse à l'aide de l'exemple suivant (extrait d'un de mes diaporamas de conférence): A C++ Mixin Example

65
Shookie

Avant d’entrer dans les détails, il est utile de décrire les problèmes qu’il essaie de résoudre. Supposons que vous essayez de modéliser un ensemble d’idées ou de concepts. Ils peuvent être liés d'une certaine manière, mais ils sont pour la plupart orthogonaux - ce qui signifie qu'ils peuvent se tenir debout indépendamment les uns des autres. Vous pouvez maintenant modéliser cela par héritage et faire en sorte que chacun de ces concepts dérive d'une classe d'interface commune. Ensuite, vous fournissez des méthodes concrètes dans la classe dérivée qui implémente cette interface.

Le problème avec cette approche est que cette conception n'offre aucune manière intuitive claire de prendre chacune de ces classes concrètes et de les combiner. 

L’idée des mix-ins est de fournir un ensemble de classes primitives, chacune d’elles modélisant un concept orthogonal de base et capables de les assembler pour composer des classes plus complexes avec juste les fonctionnalités souhaitées, un peu comme des legos. Les classes primitives elles-mêmes sont destinées à être utilisées comme blocs de construction. Ceci est extensible car vous pourrez ajouter ultérieurement d'autres classes primitives à la collection sans affecter les classes existantes.

Pour revenir à C++, une technique permettant de faire cela consiste à utiliser des modèles et l'héritage. L'idée de base est de connecter ces blocs de construction en les fournissant via le paramètre template. Vous les enchaînez ensuite ensemble, par exemple. via typedef, pour former un nouveau type contenant la fonctionnalité souhaitée.

Prenant votre exemple, disons que nous voulons ajouter une fonctionnalité de restauration en haut. Voici à quoi cela pourrait ressembler:

#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
}

Vous remarquerez que j'ai apporté quelques modifications à votre original:

  • Les fonctions virtuelles ne sont vraiment pas nécessaires ici car nous savons exactement quel est notre type de classe composé au moment de la compilation.
  • J'ai ajouté un value_type par défaut pour le deuxième paramètre de modèle afin de simplifier son utilisation. De cette façon, vous n’aurez pas à continuer à taper <foobar, int> à chaque fois que vous collerez un morceau.
  • Au lieu de créer une nouvelle classe qui hérite des éléments, un simple typedef est utilisé.

Notez que ceci est censé être un exemple simple pour illustrer l'idée de mix-in. Donc, cela ne prend pas en compte les cas de coin et les usages amusants. Par exemple, effectuer une undo sans jamais définir de nombre ne se comportera probablement pas comme prévu.

En tant que sidenote, vous pourriez également trouver cet article utile.

107
greatwolf

Un mixin est une classe conçue pour fournir des fonctionnalités à une autre classe, normalement via une classe spécifiée, qui fournit les fonctionnalités de base nécessaires à ces fonctionnalités. Par exemple, considérons votre exemple:
Le mixin dans ce cas fournit la fonctionnalité d'annuler l'opération de définition d'une classe de valeur. Cette habilité est basée sur la fonctionnalité get/set fournie par une classe paramétrée (la classe Number, dans votre exemple).

Autre exemple (extrait de "programmation basée sur Mixin en 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);
  }
... 
};

Dans cet exemple, le mixin fournit les fonctionnalités de comptage des sommets, étant donné une classe de graphes qui effectue des opérations de conversion. 

Généralement, en C++, les mixins sont implémentés via le CRTP idiome. Ce fil pourrait être une bonne lecture d’une implémentation mixin en C++: Qu'est-ce que C++ Mixin-Style?

Voici un exemple de mixin utilisant le langage CRTP (Merci à @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>
{
};

Ce mixin fournit les fonctionnalités de copie hétérogène à un ensemble (hiérarchie) de classes de formes.

7
Manu343726

J'aime la réponse de grand-loup, mais je voudrais donner une mise en garde.

greatwolf a déclaré: "Les fonctions virtuelles ne sont vraiment pas nécessaires ici car nous savons exactement quel est le type de classe que nous composons au moment de la compilation." Malheureusement, vous pouvez rencontrer un comportement incohérent si vous utilisez votre objet de manière polymorphe.

Me laisser Tweak la fonction principale de son exemple:

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!
}  

En rendant la fonction "set" virtuelle, le remplacement approprié sera appelé et le comportement incohérent ci-dessus ne se produira pas.

5
Ken

Les mixins en C++ sont exprimés à l'aide du CRC (modèle de modèle curieusement récurrent) (CRTP). Cet article est un excellent aperçu de ce qu’elles fournissent par rapport aux autres techniques de réutilisation ... le polymorphisme au moment de la compilation.

4
Jesse Pepper

Cela fonctionne comme une interface et peut-être davantage comme un résumé, mais les interfaces sont plus faciles à obtenir la première fois. 

Il aborde de nombreux problèmes, mais celui que je trouve fréquemment dans le développement concerne les API externes. imagine ça. 

Vous avez une base de données d'utilisateurs, cette base de données dispose d'un certain moyen d'accéder à ses données. Maintenant, imaginez que vous avez facebook, qui dispose également d’une certaine manière d’accéder à ses données (api). 

à tout moment, votre application devra peut-être être exécutée à l'aide des données de Facebook ou de votre base de données. Donc, vous créez une interface qui dit "tout ce qui m’implémente va définitivement avoir les méthodes suivantes", vous pouvez maintenant implémenter cette interface dans votre application ...

car une interface promet que les méthodes implémentées seront déclarées par les référentiels d’implémentation, vous savez que, quel que soit le moment ou vous utilisez cette interface dans votre application, si vous basculez les données, les méthodes que vous définissez seront toujours conservées. données à travailler hors de. 

Ce modèle de travail comporte de nombreuses autres couches, mais l’essentiel est qu’il soit bon, car les données ou autres éléments persistants deviennent une partie importante de votre application et, s’ils changent sans que vous le sachiez, votre application peut tomber en panne :)

Voici un 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

Pour comprendre le concept, oubliez les cours un instant. Pensez (le plus populaire) JavaScript. Où les objets sont des tableaux dynamiques de méthodes et de propriétés. Appelable par leur nom en tant que symbole ou en tant que littéral de chaîne. Comment implémenteriez-vous cela en C++ standard en 2018? Pas facilement. Mais c’est le cœur du concept. En JavaScript, on peut ajouter et supprimer (aka mix-in) quand et comme on le souhaite. Très important: pas d'héritage de classe.

Maintenant sur C++. Le standard C++ a tout ce dont vous avez besoin, cela n’aide en rien. Évidemment, je ne vais pas écrire de langage de script pour implémenter le mixage en utilisant C++. 

Oui, c’est un bon article , mais pour inspiration seulement. Le CRTP n'est pas une panacée. Et aussi la soi-disant approche académique est ici , également (essentiellement) basée sur CRTP.

Avant de voter cette réponse, considérez peut-être mon p.o.c. code sur la baguette magique :)

0
user5560811