Présentation
Le patron Abstract Factory fournit une interface unique pour créer des familles d’objets liés sans instancier directement les classes concrètes. Il garantit que tous les objets produits par une même fabrique respectent une même sémentique et restent compatibles entre eux.
Rôles principaux
- Fabrique abstraite : déclare les méthodes de création de chaque produit d’une famille.
- Fabrique concrète : implémente ces méthodes pour une famille donnée.
- Produit abstrait : interface commune à tous les produits d’une même catégorie.
- Client : manipule uniquement les abstractions.
Exemple : thèmes d’interface graphique
Pour illustrer ce patron, nous modélisons deux familles de composants graphiques : un thème clair et un thème sombre. Chaque famille fournit un bouton et une case à cocher.
Implémentation en Java
Les interfaces des produits et de la fabrique abstraite :
public interface Bouton {
void afficher();
}
public interface CaseACocher {
void cocher();
}
public interface FabriqueTheme {
Bouton creerBouton();
CaseACocher creerCaseACocher();
}
Les produits concrets du thème clair :
public class BoutonClair implements Bouton {
@Override
public void afficher() {
System.out.println("Bouton au style clair");
}
}
public class CaseClaire implements CaseACocher {
@Override
public void cocher() {
System.out.println("Case cochée (style clair)");
}
}
La fabrique concrète correspondante :
public class FabriqueThemeClair implements FabriqueTheme {
@Override
public Bouton creerBouton() {
return new BoutonClair();
}
@Override
public CaseACocher creerCaseACocher() {
return new CaseClaire();
}
}
On définit de la même manière BoutonSombre, CaseSombre et FabriqueThemeSombre. Le client dépend uniquement des abstractions :
public class Application {
private final FabriqueTheme fabrique;
public Application(FabriqueTheme fabrique) {
this.fabrique = fabrique;
}
public void construireInterface() {
Bouton bouton = fabrique.creerBouton();
CaseACocher caseACocher = fabrique.creerCaseACocher();
bouton.afficher();
caseACocher.cocher();
}
}
public class Demo {
public static void main(String[] args) {
FabriqueTheme theme = new FabriqueThemeClair();
Application app = new Application(theme);
app.construireInterface();
}
}
Implémetnation en C++ moderne
La même architecture en C++, cette fois avec des pointeurs intelligents pour automatiser la gestion mémoire :
#include <iostream>
#include <memory>
class Bouton {
public:
virtual ~Bouton() = default;
virtual void afficher() const = 0;
};
class CaseACocher {
public:
virtual ~CaseACocher() = default;
virtual void cocher() const = 0;
};
class BoutonClair : public Bouton {
public:
void afficher() const override {
std::cout << "Bouton au style clair" << std::endl;
}
};
class CaseClaire : public CaseACocher {
public:
void cocher() const override {
std::cout << "Case cochée (style clair)" << std::endl;
}
};
class FabriqueTheme {
public:
virtual ~FabriqueTheme() = default;
virtual std::unique_ptr<Bouton> creerBouton() const = 0;
virtual std::unique_ptr<CaseACocher> creerCaseACocher() const = 0;
};
class FabriqueThemeClair : public FabriqueTheme {
public:
std::unique_ptr<Bouton> creerBouton() const override {
return std::make_unique<BoutonClair>();
}
std::unique_ptr<CaseACocher> creerCaseACocher() const override {
return std::make_unique<CaseClaire>();
}
};
Le client utilise une fabrique abstraite sans connaître les classses concrètes :
class Application {
std::unique_ptr<FabriqueTheme> fabrique;
public:
explicit Application(std::unique_ptr<FabriqueTheme> f)
: fabrique(std::move(f)) {}
void construireInterface() const {
auto bouton = fabrique->creerBouton();
auto caseACocher = fabrique->creerCaseACocher();
bouton->afficher();
caseACocher->cocher();
}
};
int main() {
Application app(std::make_unique<FabriqueThemeClair>());
app.construireInterface();
return 0;
}
En remplaçant FabriqueThemeClair par une autre fabrique concrète, on bascule toute la famille de composants sans modifier le client.