Aller au contenu

Patrons de conception/État

Un livre de Wikilivres.
Patron de conception
Catégorie : « Gang of Four »Comportement
Nom français : État
Nom anglais : State
Gérer différents états à l'aide de différentes classes


La technique de l'État est un patron de conception comportemental utilisé en génie logiciel. Ce patron de conception est utilisé entre autres lorsqu'il est souhaité pouvoir changer le comportement de l'État d'un objet sans pour autant en changer l'instance.

Principe Général

[modifier | modifier le wikicode]

La classe censée changer d'état a un lien vers une classe abstraite "État". Cette classe abstraite "État" définit les différentes méthodes qui seront à redéfinir dans les implémentations. Dans chaque classe dérivée d'État, l'appel à la méthode X pourra avoir un comportement différent.

La classe pouvant changer d'état appellera les services de sa classe d'état dont l'instance change quand le comportement de notre classe change. De plus l'instance de la classe pouvant changer d'état peut être passée en paramètre à la méthode X de sa classe d'état. Ceci permet de changer l'état de la classe pendant l'exécution de la méthode X en instanciant un nouvel état.

Ce patron permet donc à la classe de passer d'un état à l'autre de telle façon que cette dernière apparaît changer de type dynamiquement (sans changer d'instance).

Exemple : Un programme de dessin utilise une interface abstraite pour représenter un outil. Chaque instance de ses classes dérivées concrètes représente un type d'outil différent. Quand l'utilisateur change d'outil, une nouvelle instance de la classe dérivée associée est créée.

Diagramme de classes

[modifier | modifier le wikicode]

Le patron de conception État peut être représenté par le diagramme de classes UML suivant :

Diagramme de classes UML du patron de conception État

La classe Contexte utilise différentes instances des classes concrètes dérivées de la classe abstraite État afin de représenter ses différents états. Chaque état concret traite la requête de manière différente.

En reprenant l'exemple du programme de dessin :

class AbstractTool
{
public:
    virtual void MoveTo(const Point& inP) = 0;
    virtual void MouseDown(const Point& inP) = 0;
    virtual void MouseUp(const Point& inP) = 0;
};

Chaque outil a besoin de gérer les évènement de souris : déplacement, bouton enfoncé, bouton relâché. L'outil plume est alors le suivant :

class PenTool : public AbstractTool
{
public:
    PenTool() : mMouseIsDown(false) {}

    virtual void MoveTo(const Point& inP)
    {
        if(mMouseIsDown)
        {
            DrawLine(mLastP, inP);
        }
        mLastP = inP;
    }

    virtual void MouseDown(const Point& inP)
    {
        mMouseIsDown = true;
        mLastP = inP;
    }

    virtual void MouseUp(const Point& inP)
    {
        mMouseIsDown = false;
    }

private:
    bool mMouseIsDown;
    Point mLastP;
};

class SelectionTool : public AbstractTool
{
public:
    SelectionTool() : mMouseIsDown(false) {}

    virtual void MoveTo(const Point& inP)
    {
        if(mMouseIsDown)
        {
            mSelection.Set(mLastP, inP);
        }
    }

    virtual void MouseDown(const Point& inP)
    {
        mMouseIsDown = true;
        mLastP = inP;
        mSelection.Set(mLastP, inP);
    }

    virtual void MouseUp(const Point& inP)
    {
        mMouseIsDown = false;
    }

private:
    bool mMouseIsDown;
    Point mLastP;
    Rectangle mSelection;
};

Une classe utilisant le patron d'état ci-dessus :

class DrawingController
{
public:
    DrawingController() { selectPenTool(); } // Démarrer avec un outil.

    void MoveTo(const Point& inP)    { currentTool->MoveTo(inP);    }
    void MouseDown(const Point& inP) { currentTool->MouseDown(inP); }
    void MouseUp(const Point& inP)   { currentTool->MouseUp(inP);   }

    void selectPenTool()
    {
        currentTool.reset(new PenTool);
    }

    void selectSelectionTool()
    {
        currentTool.reset(new SelectionTool);
    }

private:
    std::auto_ptr<AbstractTool> currentTool;
};

L'état de l'outil de dessin est complètement représenté par une instance de la classe AbstractTool. Cela facilite l'ajout de nouveaux outils et conserve leur comportement dans les sous-classes de la classe AbstractTool.

Contre-exemple utilisant switch

[modifier | modifier le wikicode]

Le patron de conception peut être utilisé pour remplacer des instructions switch et if, qui peuvent être difficile à maintenir et moins typées.

L'exemple suivant est similaire au précédent, mais ajouter un nouvel outil dans cette version est beaucoup plus difficile.

class Tool
{
public:
    Tool() : mMouseIsDown(false) {}

    virtual void MoveTo(const Point& inP);
    virtual void MouseDown(const Point& inP);
    virtual void MouseUp(const Point& inP);

private:
    enum Mode { Pen, Selection };
    Mode mMode;
    Point mLastP;
    bool mMouseIsDown;
    Rectangle mSelection;
};

void Tool::MoveTo(const Point& inP)
{
    switch(mMode)
    {
    case Pen:
        if (mMouseIsDown)
        {
            DrawLine(mLastP, inP);
        }
        mLastP = inP;
        break;

    case Selection:
        if (mMouseIsDown)
        {
            mSelection.Set(mLastP, inP);
        }
        break;

    default:
        throw std::exception();
    }
}

void Tool::MouseDown(const Point& inP)
{
    switch(mMode)
    {
    case Pen:
        mMouseIsDown = true;
        mLastP = inP;
        break;

    case Selection:
        mMouseIsDown = true;
        mLastP = inP;
        mSelection.Set(mLastP, inP);
        break;

    default:
        throw std::exception();
    }
}

void Tool::MouseUp(const Point& inP)
{
    mMouseIsDown = false;
}