Xavier Lamorlette

Design Patterns

Sommaire :

Creational Patterns

Factory

Crée un objet dont le type dérive d'une Abstract Base Class (ABC) sans spécifier son type à la compilation.
Masque les détails des classes dérivées.

class AFactory {
public:
    std::unique_ptr<A> createA(const std::string & type);
};

Note : le pointeur est nécessaire pour le polymorphisme.

Abstract Factory

Encapsule un groupe de factories qui construisent des objets d'une même famille.
Change ou ajoute facilement (même dynamiquement) une famille d'éléments.
Mais la liste des éléments est statique.

Abstract Factory

Builder

Sépare la construction d'un object complexe de sa représentation interne.

Builder
build() {
    builder->build_part_A();
    builder->build_part_B();
}

Prototype

Création d'un objet par copie / clonage d'un prototype.
Le type de l'objet cloné est inconnu.

class Clonable {
public:
    virtual std::unique_ptr<Clonable> clone() const = 0;
};

Singleton

Force une classe à n'avoir qu'une unique instance.
Inconvénient : comme une variable globale, cela introduit un état global qui rend difficile les tests unitaires et le refactoring.

class A {
public:
    static A & Instance();

    A(const A &) = delete;
    const A & operator = (const A &) = delete;
    A(A &&) = delete;
    A && operator = (A &&) = delete;

private:
    A() = default;
    ~A() = default;
};

A & A::Instance() {
    static A instance;
    return instance;
}

Attention à l'initialisation en environnement multi-threadé. Une solution est l'initialisation statique :

static A & foo = A::Instance();

Mais s'il y a plusieurs singletons, l'ordre d'exécution de telles initialisations statiques est indéterminé.

Une solution est aussi d'utiliser les atomics de C++11.

Extensible Factory / Virtual Constructor / Factory Method

Pour ajouter dynamiquement des classes dérivées, ou utilise une map de fonctions de création.

class AFactory {
public:
    using Creator = std::function<std::unique_ptr<A>()>;
    static void RegisterCreator(const std::string & type, Creator creator);
    static std::unique_ptr<A> Create(const std::string & type);
private:
    static std::map<std::string, Creator> CreatorMap;
};

std::unique_ptr<A> AFactory::Create(const std::string & type) {
    auto itCreatorMap = CreatorMap.find(type);
    if (itCreatorMap != CreatorMap.end()) {
        return (it->second)();
    }
    return nullptr;
}

Plutôt que des fonctions de création, on peut enregistrer des prototypes (à cloner) :

    static std::map<Tag, std::unique_ptr<A>> prototypes;

Structural Patterns: Indirect Access

Comparaison

Adapter, Bridge et Proxy permettent l'accès indirect à un objet par un intermédiaire.

Adapter / Wrapper

Transforme une interface en une autre.
Permet de séparer une application de l'API particulière d'une bibliothèque.
Permet de fusionner plusieurs API / hiérarchies d'objets.

Class adapter : héritage privé de la classe originale :

Composite

Object adapter : composition de l'objet original :

Composite

Bridge

Découple l'interface de l'implémentation pour qu'elles puissent évoluer indépendemment.
Cache les détails d'implémentations (types opaques).

Bridge

Proxy

Aussi appelé Surrogate.
Interface (substitut) qui transmet tous les appels à une autre classe.

Utilisations :

Proxy

Structural Patterns

Composite

Agrégration récursive.
Traitement uniforme des objects simples et des objets composites.

Composite

Decorator

Ajout dynamique de comportements complémentaires à un objet.
Ces comportements peuvent être ajoutés incrémentalement.
Évite l'explosion du nombre de classes lorsque l'on combine plusieurs ajouts de comportements.

Decorator
Decorator::operation() override {
    object->operation();
}

Decoration_A::operation() override {
    Decorator::operation();
    added_operation();
}

Façade

Interface simplifiée / uniformisée d'un ensemble de classes.

Façade

Encapsulating façade : les classes sous-jacentes ne sont pas accessibles.

Flyweight

Poids-mouche : partage de l'état commun et invariant (intrinsèque) de multiples objects.
Réduit le coût mémoire d'un ensemble d'objets similaires.

Flyweight
Flyweight_factory::create_flyweight(key) {
    if flyweight[key] exits
        then return it
    else allocate it in pool, and return it
}

Un flyweight est immuable.
Les paramètres dissemblables (extrinsèques) des objets sont extraits et passés en paramètres des méthodes.

Behavioral Patterns

Chain of Responsibility

Chaque élément peut traiter la demande ou la transmettre au suivant.
Permet de découpler le client et l'élément qui va traiter la requête.

Chain of Responsibility

Command

Réification (encapsulation dans un objet) d'une fonction.
Découple l'appel d'une opération de son exécution.

Command
Concrete_command::execute() {
    receiver->action();
}

Interpretor

Interprétation d'un message à partir de la grammaire de son langage.

Iterator

Accès séquentiel aux éléments d'un composite, sans exposer sa représentation sousjacente.

Mediator

Encapsulation dans un objet des communications entre objets.

Memento

Enregistrement de l'état d'un objet pour pouvoir le restaurer ultérieurement.

class Originator {
public:
    Memento * save_state() const;
    void restore_state(Memento);
private:
    State state;
};

class Memento {
public:
    ~Memento();
private:
    friend class Originator;
    Memento(State);
    State state;
};

Observer

Mise à jour des dépendances lorsque l'état d'un objet change.
Permet de découpler le sujet des observateurs.

Observer
Subject::notify() {
    for (observer: observers)
        observer->update();
}

C'est un cas particulier du paradigme Publisher / Subscriber :

class Observer {
public:
    virtual void update(int message) = 0;
};

class Subject {
public:
    virtual void subscribe(int message, Observer * observer);
    virtual void notify(int message);
private:
    using ObserversList = std::vector<Observer *>;
    using ObserversMap = std::map<int, ObserversList>;
    ObserversMap observers;
};

Les observers peuvent être :

State

Changement du comportement d'un objet en fonction de son état interne.

State
Object::operation() {
    state->operation();
}

Strategy

Réification d'une famille d'algorithmes interchangeables.

Strategy

En C++, la stratégie peut être une classe template utilisée comme argument template, comme dans la STL :

template<typename CharT,
    typename Traits = char_traits<CharT>,
    typename Allocator = allocator<CharT>>
class basic_string { … };
class basic_string<char> string;

Template Method

Définition du squelette d'un algorithme.

Template

Principe d'Hollywood : « Ne nous appelez pas, on vous appellera. »

Visitor

Extraction des opérations sur une hiérarchie, ce qui facilite l'ajout de nouvelles opérations.

Visitor

En C++, on réalise cela avec un “double dispatch” :

{
    Concrete_object object;
    Object & abstract_object = object;
    Concrete_visitor visitor;
    visitor.visit(abstract_object);
}

Visitor::visit(Object & object) {
    object.accept(* this);
}

Concrete_object::accept(Visitor & visitor) override {
    visitor.visit_concrete(* this);
}

Le visitor permet de réalister une opération sur chaque élément d'une structure. Le parcours de la structure peut être fait :

La dernière mise à jour de cette page date de février 2022.

Le contenu de ce site est, en tant qu'œuvre originale de l'esprit, protégé par le droit d'auteur.
Pour tout commentaire, vous pouvez m'écrire à xavier.lamorlette@gmail.com.