Qu'est-ce que les Mixins (en tant que concept)

j'essaie de me concentrer sur le concept de Mixin mais je n'arrive pas à comprendre ce que c'est. Pour moi, 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?

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

63
demandé sur Shookie 2013-09-13 00:03:27

6 réponses

avant d'entrer dans 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 d'modèle. Ils peuvent être liés d'une certaine façon, mais ils sont orthogonaux pour la plupart -- ce qui signifie qu'ils peuvent se tenir debout indépendamment l'un de l'autre. Maintenant, vous pourriez modéliser ceci par héritage et avoir chacun de ces concepts dérivent d'une certaine classe d'interface commune. Puis vous fournir des méthodes de la classe dérivée implémente cette interface.

le problème avec cette approche est que cette conception n'offre aucune façon intuitive claire de prendre chacune de ces classes concrètes et de les combiner ensemble.

l'idée avec des mixages est de fournir un tas de classes primitives, où chacun d'eux modèle un concept orthogonal de base, et être en mesure de les coller ensemble pour composer des classes plus complexes avec juste la fonctionnalité que vous voulez -- sorte de comme legos. La primitive les cours eux-mêmes sont destinés à être utilisés comme éléments de construction. Ceci est extensible car plus tard vous pouvez ajouter d'autres classes primitives à la collection sans affecter les classes existantes.

pour revenir au c++, une technique pour faire ceci est d'utiliser des modèles et l'héritage. L'idée de base ici est que vous connectez ces blocs de construction ensemble en les fournissant via le paramètre template. Vous les enchaînez alors ensemble, par exemple. par typedef , pour former un nouveau type contenant fonctionnalité que vous voulez.

en prenant votre exemple, disons que nous voulons ajouter une fonctionnalité redo sur le dessus. Voici à quoi ça 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 fait quelques changements à partir de votre original:

  • les fonctions virtuelles ne sont vraiment pas nécessaires ici parce que nous savons exactement quel est notre type de classe composé au moment de la compilation.
  • j'ai ajouté un défaut value_type pour le second template param pour rendre son usage moins encombrant. De cette façon, vous n'avez pas à taper <foobar, int> chaque fois que vous collez un morceau ensemble.
  • au lieu de créer une nouvelle classe qui hérite des pièces, un simple typedef est utilisé.

notez que ceci se veut un exemple simple pour illustrer l'idée de mixage. Donc il ne tient pas compte des cas de corner et des usages drôles. Par exemple, exécuter un undo sans un numéro ne se comportera probablement pas comme on pourrait s'y attendre.

comme un sidenote, vous pourriez également trouver cet article utile.

103
répondu greatwolf 2013-09-12 22:38:14

un mixin est une classe conçue pour fournir des fonctionnalités à une autre classe, normalement par l'intermédiaire d'une classe spécifiée qui fournit les fonctionnalités de base dont la fonctionnalité a besoin. Par exemple, considérez votre exemple:

Le mixin dans ce cas fournit la fonctionnalité de défaire l'opération définie d'une classe de valeur. Cette qualité est basée sur la fonctionnalité get/set fournie par une classe paramétrée (la classe Number , dans votre exemple).

un Autre exemple (Extrait de " Mixin base de la programmation 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 Counting vertices , étant donné une classe de graphe qui effectue des opérations trasversales.

généralement, en C++, les mixins sont implémentés par le CRTP idiom. Ce fil pourrait être une bonne lecture sur un mixin implementation in C++: Qu'est-ce que c++ Mixin-Style?

voici un exemple de mixin qui tire profit de l'idiome du 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.

7
répondu Manu343726 2017-05-23 11:33:27

j'aime la réponse de greatwolf, mais je voudrais faire une mise en garde.

, a déclaré greatwolf, " les fonctions virtuelles ne sont vraiment pas nécessaires ici parce que nous savons exactement quel est notre type de classe composé au moment de la compilation."Malheureusement, vous pouvez rencontrer un comportement incohérent si vous utilisez votre objet polymorphiquement.

permettez-moi de 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 faisant le " jeu" fonction virtuelle, le bon correcteur et le comportement incohérent ci-dessus ne se produira pas.

5
répondu Ken 2014-02-10 23:57:04

les Mixines en C++ sont exprimées en utilisant le curieusement récurrent Pattern (CRTP). Ce post est une excellente répartition de ce qu'ils fournissent sur les autres techniques de réutilisation... polymorphisme dans le temps de compilation.

4
répondu Jesse Pepper 2015-03-06 02:34:00

cela fonctionne comme une interface et peut-être plus comme un abstrait, mais les interfaces sont plus faciles à obtenir pour la première fois.

il aborde de nombreuses questions, mais un que je trouve dans le développement qui vient beaucoup est apis externes. imaginer cela.

Vous avez une base de données d'utilisateurs, cette base de données a une certaine façon d'obtenir l'accès à ses données. maintenant, imaginez que vous avez facebook, qui a aussi une certaine façon d'obtenir l'accès à ses données (api).

à tout moment votre application peut avoir besoin d'exécuter en utilisant des données de facebook ou de votre base de données. donc, ce que vous faites est de créer une interface qui dit "tout ce qui me met en œuvre va certainement avoir les méthodes suivantes" maintenant, vous pouvez mettre en œuvre cette interface dans votre application...

parce qu'une interface promet que les dépôts de mise en œuvre auront les méthodes déclarées dans eux, vous savez que partout ou chaque fois que vous utilisez cette interface dans votre application, si vous commutez les données, il va toujours avoir les méthodes que vous définissez et donc avoir des données pour travailler hors.

il y a beaucoup plus de couches à ce modèle de travail, mais l'essence est qu'il est bon parce que les données ou d'autres éléments persistants deviennent une grande partie de votre application, et si elles changent sans que vous le sachiez, votre application peut casser :)

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
répondu Jimmyt1988 2013-09-12 20:36:41

pour comprendre le concept oubliez les cours un instant. Pense que (la plus populaire) de JavaScript. Où les objets sont des tableaux dynamiques de méthodes et de propriétés. Appelable par leur nom comme symbole ou comme chaîne littérale. Comment mettriez-vous cela en C++ standard en 2018? pas facilement . Mais c'est le cœur du concept. En JavaScript on peut ajouter et supprimer (alias mix-in) quand et quoi que l'on souhaite. Très important: aucun héritage de classe.

maintenant en C++. Standard c++ a tout ce dont vous avez besoin, n'aide pas comme une déclaration ici. Évidemment, je n'écrirai pas de langage de script pour implémenter mix-in en utilisant C++.

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

avant de voter cette réponse peut-être considérer mon p. O. C. code sur la case :)

0
répondu 2018-06-28 11:57:16