Design patterns essentiels : Singleton, Factory, Observer, MVC
Qu'est-ce qu'un design pattern ?
Un design pattern (patron de conception) est une solution éprouvée à un problème récurrent de conception logicielle. Ce n'est pas du code prêt à copier-coller, mais un modèle adaptable. Les 23 patterns du Gang of Four (GoF, 1994) sont classés en 3 familles : créationnels, structurels, comportementaux.
Patterns créationnels
Singleton
Problème : garantir qu'une classe n'a qu'une seule instance dans toute l'application.
Usage : connexion BDD, logger, configuration, cache.
public class DatabaseConnection {
private static DatabaseConnection instance;
private DatabaseConnection() { } // Constructeur privé
public static synchronized DatabaseConnection getInstance() {
if (instance == null) {
instance = new DatabaseConnection();
}
return instance;
}
}
// Utilisation
DatabaseConnection db = DatabaseConnection.getInstance();Le constructeur est privé → impossible de faire new. L'accès se fait via getInstance() qui crée l'instance au premier appel et la réutilise ensuite.
Attention : le Singleton est souvent considéré comme un anti-pattern car il crée un état global et complique les tests. Préférer l'injection de dépendances.
Factory (Fabrique)
Problème : créer des objets sans exposer la logique d'instanciation au code client.
Usage : créer des objets dont le type dépend d'un paramètre.
interface Notification {
public function envoyer(string $message): void;
}
class EmailNotification implements Notification { /* ... */ }
class SmsNotification implements Notification { /* ... */ }
class SlackNotification implements Notification { /* ... */ }
class NotificationFactory {
public static function create(string $type): Notification {
return match($type) {
'email' => new EmailNotification(),
'sms' => new SmsNotification(),
'slack' => new SlackNotification(),
default => throw new \InvalidArgumentException("Type inconnu : $type"),
};
}
}
// Le client ne connaît pas les classes concrètes
$notif = NotificationFactory::create('email');
$notif->envoyer("Bienvenue !");Avantage : ajouter un nouveau type ne modifie que la Factory, pas le code client.
Patterns structurels
Adapter (Adaptateur)
Problème : faire collaborer deux interfaces incompatibles.
Usage : intégrer une librairie tierce dont l'API ne correspond pas à votre interface.
// Interface attendue par votre code
interface JsonExporter {
String exportToJson(Object data);
}
// Librairie tierce avec une API différente
class XmlLibrary {
public String convertToXml(Object data) { /* ... */ }
}
// Adapter qui fait le pont
class XmlToJsonAdapter implements JsonExporter {
private XmlLibrary xmlLib = new XmlLibrary();
public String exportToJson(Object data) {
String xml = xmlLib.convertToXml(data);
return convertXmlToJson(xml); // Transformation
}
}Decorator (Décorateur)
Problème : ajouter des responsabilités à un objet dynamiquement, sans modifier sa classe.
Usage : middleware HTTP, flux d'entrée/sortie (Java I/O), logging.
# Décorateurs Python (syntaxe native)
def log_execution(func):
def wrapper(*args, **kwargs):
print(f"Appel de {func.__name__}")
result = func(*args, **kwargs)
print(f"Résultat : {result}")
return result
return wrapper
@log_execution
def calculer_prix(montant, taxe):
return montant * (1 + taxe)Patterns comportementaux
Observer (Observateur)
Problème : quand un objet change d'état, notifier automatiquement tous les objets qui en dépendent.
Usage : événements UI, systèmes de notifications, EventDispatcher Symfony.
interface Observer {
void onEvent(String event, Object data);
}
class EventDispatcher {
private Map<String, List<Observer>> listeners = new HashMap<>();
public void subscribe(String event, Observer observer) {
listeners.computeIfAbsent(event, k -> new ArrayList<>()).add(observer);
}
public void dispatch(String event, Object data) {
for (Observer obs : listeners.getOrDefault(event, List.of())) {
obs.onEvent(event, data);
}
}
}Symfony utilise ce pattern avec EventSubscriberInterface et le composant EventDispatcher.
Strategy (Stratégie)
Problème : définir une famille d'algorithmes interchangeables.
Usage : algorithmes de tri, méthodes de paiement, stratégies d'export.
interface SortStrategy {
public function sort(array &$data): void;
}
class BubbleSort implements SortStrategy { /* ... */ }
class QuickSort implements SortStrategy { /* ... */ }
class MergeSort implements SortStrategy { /* ... */ }
class DataProcessor {
public function __construct(private SortStrategy $strategy) {}
public function process(array $data): array {
$this->strategy->sort($data);
return $data;
}
}
// Changer d'algorithme sans modifier DataProcessor
$processor = new DataProcessor(new QuickSort());Le pattern MVC
Model-View-Controller est le pattern architectural le plus utilisé pour les applications web.
| Composant | Rôle | Exemple Symfony |
|---|---|---|
| Model | Données + logique métier | Entités Doctrine, Services |
| View | Affichage, interface utilisateur | Templates Twig, JSON |
| Controller | Reçoit les requêtes, orchestre | Contrôleurs Symfony |
Flux MVC
- L'utilisateur envoie une requête HTTP
- Le Controller reçoit la requête, appelle le Model
- Le Model traite la logique métier, accède aux données
- Le Controller passe les données à la View
- La View génère la réponse (HTML, JSON)
Variantes
MVP (Model-View-Presenter) : le Presenter remplace le Controller, la View est passive. MVVM (Model-View-ViewModel) : binding bidirectionnel entre View et ViewModel (Angular, Vue.js).
Points clés pour le concours
- Singleton : une seule instance (mais préférer l'injection de dépendances)
- Factory : création d'objets sans exposer les classes concrètes
- Observer : notification automatique des dépendants (= événements)
- Strategy : algorithmes interchangeables sans modifier le client
- MVC : séparation Model (données) / View (affichage) / Controller (orchestration)
- Les patterns sont des solutions réutilisables, pas du code à copier