88 votes

Que sont les Mixins (en tant que concept)

J'essaie de comprendre le concept de Mixin, mais je n'arrive pas à comprendre ce que c'est. D'après moi, il s'agit d'un moyen d'étendre les capacités d'une classe en utilisant l'héritage. J'ai lu que les gens les appellent des "sous-classes abstraites". Quelqu'un peut-il m'expliquer pourquoi ?

J'apprécierais que vous expliquiez votre réponse sur la base de l'exemple suivant (tiré d'un de mes diaporamas de cours) : A C++ Mixin Example

150voto

greatwolf Points 9561

Avant d'expliquer ce qu'est un mix-in, il est utile de décrire les problèmes qu'il tente de résoudre. Disons que vous avez un tas d'idées ou de concepts que vous essayez de modéliser. Ils peuvent être liés d'une manière ou d'une autre, mais ils sont orthogonaux pour la plupart, ce qui signifie qu'ils peuvent être utilisés indépendamment les uns des autres. Vous pourriez modéliser cela par l'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 de cette approche est que cette conception n'offre aucun moyen intuitif clair de prendre chacune de ces classes concrètes et de les combiner ensemble.

L'idée avec les mix-ins est de fournir un tas de classes primitives, où chacune d'entre elles modélise un concept orthogonal de base, et de pouvoir les coller ensemble pour composer des classes plus complexes avec juste la fonctionnalité que vous voulez -- un peu comme des legos. Les classes primitives elles-mêmes sont destinées à être utilisées comme des blocs de construction. Elles sont extensibles puisque, plus tard, vous pourrez ajouter d'autres classes primitives à la collection sans affecter les classes existantes.

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

En reprenant votre exemple, disons que nous voulons ajouter une fonctionnalité de refonte. 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
}

Tu remarqueras que j'ai fait quelques changements par rapport à ton original :

  • Les fonctions virtuelles ne sont pas vraiment nécessaires ici car nous savons exactement quel est le type de notre classe composée au moment de la compilation.
  • J'ai ajouté une valeur par défaut value_type pour le second paramètre du modèle afin de rendre son utilisation moins contraignante. Ainsi, vous n'aurez pas à taper sans cesse <foobar, int> à chaque fois que vous collez une pièce ensemble.
  • Au lieu de créer une nouvelle classe qui hérite des pièces, une simple typedef est utilisé.

Notez qu'il s'agit d'un exemple simple pour illustrer l'idée du mixage. Il ne prend donc pas en compte les cas particuliers et les utilisations amusantes. Par exemple, l'exécution d'un undo sans jamais définir un nombre ne se comportera probablement pas comme vous l'attendez.

À titre d'information, vous pourriez également trouver cet article utile.

9voto

Ken Points 1

J'aime la réponse de greatwolf, mais je voudrais faire 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 notre classe composée au moment de la compilation." Malheureusement, vous pouvez rencontrer un comportement incohérent si vous utilisez votre objet de manière polymorphe.

Laissez-moi modifier 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.

8voto

Manu343726 Points 8803

Un mixin est une classe conçue pour fournir une fonctionnalité à une autre classe, normalement par le biais d'une classe spécifique qui fournit les caractéristiques de base dont la fonctionnalité a besoin. Prenons par exemple votre exemple :
Dans ce cas, le mixin permet d'annuler l'opération de définition d'une classe de valeurs. Cette fonctionnalité est basée sur le get/set la fonctionnalité fournie par une classe paramétrée (La Number dans votre exemple).

Un autre exemple (extrait de " Programmation basée sur les mixins 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 la fonctionnalité de comptage des sommets , étant donné une classe de graphe qui effectue des opérations trasversales.

En général, en C++, les mixins sont implémentés par l'intermédiaire de la fonction CRTP idiome. Ce fil de discussion pourrait être une bonne lecture sur l'implémentation d'un mixin en C++ : Qu'est-ce que le C++ Mixin-Style ?

Voici un exemple de mixin qui tire parti de l'idiome 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 la fonctionnalité de copie hétérogène à un ensemble (hiérarchie) de classes de formes.

3voto

Jesse Pepper Points 2100

Les mixins en C++ sont exprimés à l'aide de l'élément Patron de modèle curieusement récurrent (CRTP). Ce poste est une excellente analyse de ce qu'ils apportent par rapport aux autres techniques de réutilisation... le polymorphisme à la compilation.

0voto

Pour comprendre le concept, oubliez les classes pendant un moment. Pensez au JavaScript (le plus populaire). Les objets sont des tableaux dynamiques de méthodes et de propriétés. Ils peuvent être appelés par leur nom sous forme de symbole ou de chaîne littérale. Comment implémenteriez-vous cela en C++ standard en 2018 ? Pas facilement . Mais c'est le cœur du concept. En JavaScript, il est possible d'ajouter et de supprimer (c'est-à-dire de mélanger) tout ce que l'on veut et quand on le veut. Très important : pas d'héritage de classe.

Passons maintenant au C++. Le C++ standard a tout ce dont vous avez besoin, ne sert pas d'affirmation ici. Il est évident que je n'écrirai pas un langage de script pour mettre en œuvre le mix-in en utilisant C++.

Oui, c'est un bon article mais pour l'inspiration seulement. Le CRTP n'est pas une panacée. Et l'approche dite académique est également aquí également (en substance) basé sur le CRTP.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X