⚙️ Développement

Design patterns essentiels : Singleton, Factory, Observer, MVC

📅 10 avril 202616 min de lecture
design-patternpoomvcjavaphp
Série : Algorithmique et POO

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.

ComposantRôleExemple Symfony
ModelDonnées + logique métierEntités Doctrine, Services
ViewAffichage, interface utilisateurTemplates Twig, JSON
ControllerReçoit les requêtes, orchestreContrôleurs Symfony

Flux MVC

  1. L'utilisateur envoie une requête HTTP
  2. Le Controller reçoit la requête, appelle le Model
  3. Le Model traite la logique métier, accède aux données
  4. Le Controller passe les données à la View
  5. 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