IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Présentation des principaux design patterns en C++


précédentsommairesuivant

V. Le décorateur

Définition ; Les décorateurs sont l'ensemble des classes permettant d'étendre dynamiquement le rôle d'une classe de base.

V-A. Pourquoi le décorateur

Imaginez que vous êtes en train de coder un programme qui gère des commandes de nourriture dans un café : des gaufres, des crêpes avec du sucre, du nutella et qu'en plus, ce café gère un stock de fromages. Comment modéliser ces objets ?

On pourrait créer autant de classes qu'il y a de possibilités, mais ceci est fort gênant puisqu'il va y avoir une explosion du nombre de classes et une redondance inimaginable au niveau du code. Pour résoudre ce problème, on préfère le pattern décorateur. Grâce à lui, vous pourrez étendre de façon dynamique et non intrusive le rôle d'une classe. Pour cela, on va wrapper l'objet à décorer avec les décorateurs. Mais pour continuer à utiliser notre objet de base sans changement, il faut que toutes les classes dérivent d'un même composant de base. On en déduit donc le diagramme suivant :

Diagramme UML du DP décorateur
Diagramme UML du pattern décorateur

V-B. Implémentation première des décorateurs

Avec l'analyse précédente, on arrive au code suivant :

 
Sélectionnez
#ifndef DECO_H
#define DECO_H

#include <string>
#include <iostream>

//==========================================================================================
class BaseAliment 
{
protected:
    //les données de la classe de base
    double m_prix;
    std::string m_name;

    virtual ~BaseAliment() = 0;
    BaseAliment(double prix=0,const std::string& name="");
        
public:

    virtual double      GetPrix() const;
    virtual std::string GetName() const; 

    virtual void Afficher(std::ostream & flux) const;
};

class Aliment : public BaseAliment
{

public:
    Aliment(double prix=0,const std::string& name="");
    virtual ~Aliment();
};
//==========================================================================================
//l'aliment à proprement parler. ici trivial.Il pourrait être substitué par BaseAliment
class Decorateur : public BaseAliment
{
protected:
    BaseAliment& m_base;

    Decorateur(BaseAliment& base,double prix=0,const std::string& name="");
    virtual ~Decorateur() =0;
public:
    BaseAliment* GetBase() const {return &m_base;};
};

//un premier décorateur permettant d'ajouter du goût à un aliment 
//Ce décorateur modifie les fonctions déjà présentes
class DecorateurGout : public Decorateur
{
public:

    double      GetPrix() const;
    std::string GetName() const; 

    DecorateurGout(BaseAliment& base,double prix,const std::string& name);
    ~DecorateurGout();
};

//un deuxième décorateur permettant d'ajouter une date limite à un aliment 
//Ce décorateur ajoute une nouvelle fonction.
class DecorateurPerissable : public Decorateur
{
    std::string m_limite;

public:
    std::string GetLimite() const {return (m_limite) ;}        

    void Afficher(std::ostream & flux) const;

    DecorateurPerissable(BaseAliment& base,const std::string& limite);
    ~DecorateurPerissable(){}
};

#endif

Et le fichier décorateur.cpp:

 
Sélectionnez
#include "deco.h"

using namespace std;

BaseAliment::~BaseAliment()
{

}

Aliment::~Aliment()
{

}

DecorateurGout::~DecorateurGout()
{

}
Decorateur::~Decorateur()
{

}


BaseAliment::BaseAliment(double prix,const std::string& name):
m_prix(prix),m_name(name)
{

}

Aliment::Aliment(double prix,const std::string& name):
BaseAliment(prix,name)
{

}

Decorateur::Decorateur(BaseAliment& base,double prix,const std::string& name):
BaseAliment(prix,name),m_base(base)
{

}

DecorateurGout::DecorateurGout(BaseAliment& base,double prix,const std::string& name):
Decorateur(base,prix,name)
{

}

DecorateurPerissable::DecorateurPerissable(BaseAliment& base,const std::string& limite):
Decorateur(base),m_limite(limite)
{

}

double  BaseAliment::GetPrix() const 
{
    return m_prix;
}
std::string BaseAliment::GetName() const 
{
    return m_name;
}

void BaseAliment::Afficher(std::ostream & flux) const
{        
    flux<<GetName() << " :" << GetPrix();
}    


double DecorateurGout::GetPrix() const 
{
    return (m_base.GetPrix()+m_prix);
}

std::string DecorateurGout::GetName() const 
{
    return (m_base.GetName()+" "+m_name);
}

void DecorateurPerissable::Afficher(std::ostream & flux) const
{
    m_base.Afficher(flux);
    flux<<" "<<m_limite;
}

std::ostream & operator << (std::ostream & flux,const BaseAliment& t)
{

    t.Afficher(flux);
    return flux;
}

int main(int argc,char* argv[])
{
    BaseAliment *c = new Aliment(1.5,"Gauffre");
    cout<< (*c)<<endl;
    c = new DecorateurGout(*c,0.2,"Sucre");
    cout<<*c<<endl;
    c = new DecorateurGout(*c,0.3,"Nutella");
    cout<<*c<<endl;

    cout<<endl;

    BaseAliment *d = new Aliment(2.0,"Fromage");
    cout<<(*d)<<endl;


    d = new DecorateurGout(*d,15,"Puant");

    cout<<(*d)<<endl;        
    d = new DecorateurPerrisable(*d,"31/12/07");
    cout<<(*d)<<endl;
}

Ce code est moins facile que le précédent.

Tout d'abord le destructeur virtuel pur avec une définition. Essayons de comprendre pourquoi il en est ainsi. Avant toute chose, il faut remarquer que BaseAliment est ici une interface. Pour l'empêcher d'être instanciée, il faut donc qu'elle possède une fonction membre virtuelle pure. Mais le seul problème, c'est que toutes les fonctions membres ont une définition de base. On pourrait ajouter une fausse fonction membre abstraite, mais cela serait du toc et une grave faute de conception. Le choix du destructeur est alors un bon choix. Grâce à lui, vous pouvez disposer d'un destructeur qui effectue son rôle puisqu'il est bien défini tout en ayant une classe abstraite.

Ensuite, le pattern en lui-même. Il se base sur le mélange héritage/composition. Pour cela, on définit tout d'abord une classe de base (ici BaseAliment) qui fournit aux autres classes une interface à respecter. Ensuite, à partir de cette classe, on définit un composant (Aliment) et une classe décorateur qui va servir d'interface à tous les décorateurs.

Puis à partir de la classe Décorateur, on crée de nouvelles classes qui représentent le décorateur concret. Chaque décorateur concret offre une nouvelle fonction à l'objet qu'il reçoit en paramètre dans son constructeur. Cette nouvelle fonction peut être soit une extension d'une fonction existante (ajouter un nouveau goût pour DecorateurGout) ou créer une nouvelle fonction pour DécorateurLimite.

Dans le cas où la décoration modifie une fonction préexistante, elle s'exécute toujours en appelant la fonction de l'objet décoré et en ajoutant un calcul avant ou après celle-ci.

En pratique, on chaîne souvent les constructions comme suit, bien que cela puisse réduire la lisibilité :

 
Sélectionnez
BaseAliment *c= new  DecorateurGout( *(new Aliment(1.5,"Gauffre")),0.2,"Sucre") ;

V-C. Remarques sur le décorateur

V-C-1. Explosion du nombre de classes

Bien que le nombre de classes soit moins important qu'avec la méthode exhaustive le nombre de classes reste quand même assez conséquent. Pour pallier ce problème, on couple souvent le décorateur avec une fabrique de telle sorte que l'on ne crée quasiment plus rien à la main.

V-C-2. Pas de template

Le décorateur est l'un des rares patterns qui ne se template pas facilement. En effet, les patterns précédents offraient des points invariants, quelle que soit l'implémentation. Or ici, le décorateur n'offre pas ces points invariants puisqu'il présente plutôt un concept. Il est donc beaucoup plus difficile à mettre sous forme de template.


précédentsommairesuivant

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2007 Côme David. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.