Xavier Lamorlette
Sommaire :
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.
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.
Sépare la construction d'un object complexe de sa représentation interne.
build() {
builder->build_part_A();
builder->build_part_B();
}
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;
};
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.
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;
Adapter, Bridge et Proxy permettent l'accès indirect à un objet par un intermédiaire.
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 :
Object adapter : composition de l'objet original :
Découple l'interface de l'implémentation pour qu'elles puissent évoluer indépendemment.
Cache les détails d'implémentations (types opaques).
Aussi appelé Surrogate.
Interface (substitut) qui transmet tous les appels à une autre classe.
Utilisations :
Agrégration récursive.
Traitement uniforme des objects simples et des objets composites.
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::operation() override {
object->operation();
}
Decoration_A::operation() override {
Decorator::operation();
added_operation();
}
Interface simplifiée / uniformisée d'un ensemble de classes.
Encapsulating façade : les classes sous-jacentes ne sont pas accessibles.
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_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.
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.
Réification (encapsulation dans un objet) d'une fonction.
Découple l'appel d'une opération de son exécution.
Concrete_command::execute() {
receiver->action();
}
Interprétation d'un message à partir de la grammaire de son langage.
Accès séquentiel aux éléments d'un composite, sans exposer sa représentation sousjacente.
Encapsulation dans un objet des communications entre objets.
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;
};
Mise à jour des dépendances lorsque l'état d'un objet change.
Permet de découpler le sujet des observateurs.
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 :
update()
;Changement du comportement d'un objet en fonction de son état interne.
Object::operation() {
state->operation();
}
Réification d'une famille d'algorithmes interchangeables.
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;
Définition du squelette d'un algorithme.
Principe d'Hollywood : « Ne nous appelez pas, on vous appellera. »
Extraction des opérations sur une hiérarchie, ce qui facilite l'ajout de nouvelles opérations.
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 :
Composite::accept(Visitor visitor) {
visitor.visit_concrete(* this);
for (node: children)
node.accept(visitor);
}
Visitor::visit_concrete(Composite composite) {
for (node: composite.children)
node.accept(* this);
}
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.