Forte cohésion / Faible couplage

Notions théoriques
La forte cohésion et le faible couplage sont 2 principes fondamentaux de la programmation orientée objet. Ils guident la façon dont nous organisons nos classes pour obtenir un code plus lisible, plus maintenable et plus évolutif.
Ces 2 principes vont souvent de pair : une classe fortement cohésive est naturellement plus facile à isoler, et donc à faiblement coupler avec le reste du système.
La forte cohésion
Définition :
La forte cohésion signifie qu'une classe ne fait qu'une seule chose, et la fait bien. Toutes ses méthodes et propriétés sont directement liées à sa responsabilité principale.
C'est le principe de responsabilité unique : une classe = une responsabilité.
Mauvais exemple (faible cohésion) :
class Personnage {
private $nom;
private $pointsDeVie;
// Responsabilité 1 : gérer les données du personnage
public function getNom() { return $this->nom; }
public function setPointsDeVie($pv) { $this->pointsDeVie = $pv; }
// Responsabilité 2 : afficher des logs (ne devrait pas être ici !)
public function logAttaque($message) {
echo "[LOG] " . date('H:i:s') . " - " . $message . "\n";
}
// Responsabilité 3 : sauvegarder en base de données (ne devrait pas être ici !)
public function sauvegarder() {
// connexion à la base de données...
}
}
Bon exemple (forte cohésion) :
// Chaque classe a une seule responsabilité
class Personnage {
private $nom;
private $pointsDeVie;
public function getNom() { return $this->nom; }
public function setPointsDeVie($pv) { $this->pointsDeVie = $pv; }
}
class Logger {
public function log($message) {
echo "[LOG] " . date('H:i:s') . " - " . $message . "\n";
}
}
class PersonnageRepository {
public function sauvegarder(Personnage $personnage) {
// connexion à la base de données...
}
}
Si vous hésitez à nommer votre classe, ou si son nom contient "Et" ou "Ou", c'est souvent un signe qu'elle porte trop de responsabilités.
Le faible couplage
Définition :
Le faible couplage signifie que les classes dépendent peu les unes des autres. Une modification dans une classe n'entraîne pas une cascade de modifications dans les autres.
Le couplage représente le degré de dépendance entre les classes. Un faible couplage rend le code plus flexible et plus testable.
Mauvais exemple (fort couplage) :
class Game {
private $logger;
public function __construct() {
// Game crée elle-même son Logger : fort couplage !
$this->logger = new Logger();
}
public function startGame() {
$this->logger->log("Le jeu commence !");
}
}
Si on veut changer de Logger (par exemple utiliser un FileLogger), on doit modifier Game.
Bon exemple (faible couplage) :
class Game {
private $logger;
// Game reçoit son Logger par injection : faible couplage
public function __construct(Logger $logger) {
$this->logger = $logger;
}
public function startGame() {
$this->logger->log("Le jeu commence !");
}
}
// On peut changer de Logger sans modifier Game
$logger = new Logger();
$game = new Game($logger);
$game->startGame();
Le faible couplage s'obtient souvent grâce à l'injection de dépendances : au lieu que la classe crée elle-même ses dépendances avec new, elle les reçoit en paramètre.
Résumé des 2 principes
| Principe | Question à se poser | Objectif |
|---|---|---|
| Forte cohésion | "Ma classe fait-elle une seule chose ?" | Chaque classe = une responsabilité |
| Faible couplage | "Mes classes dépendent-elles trop les unes des autres ?" | Limiter les dépendances entre classes |
Exemple pratique
Dans notre jeu de combat, imaginons une classe Game qui gère à la fois le déroulement du combat et les logs. C'est un exemple de faible cohésion et de fort couplage.
Avant refactorisation (problématique) :
class Game {
private $fighter1;
private $fighter2;
public function __construct() {
$this->fighter1 = new Fighter("Guerrier"); // Fort couplage
$this->fighter2 = new Fighter("Mage"); // Fort couplage
}
public function startGame() {
echo "[LOG] Debut du combat\n"; // Responsabilite de log melangee
$this->fighter1->attack();
echo "[LOG] " . $this->fighter1->getNom() . " a attaque\n";
}
}
Après refactorisation (forte cohésion + faible couplage) :
class Logger {
public function log($message) {
echo "[LOG] " . $message . "\n";
}
}
class Game {
private $fighter1;
private $fighter2;
private $logger;
// Injection des dépendances : faible couplage
public function __construct(Fighter $fighter1, Fighter $fighter2, Logger $logger) {
$this->fighter1 = $fighter1;
$this->fighter2 = $fighter2;
$this->logger = $logger;
}
// Game ne gère que le combat : forte cohésion
public function startGame() {
$this->logger->log("Debut du combat !");
$this->fighter1->attack();
$this->logger->log($this->fighter1->getNom() . " a attaque");
}
}
// main.php
$fighter1 = new Fighter("Guerrier");
$fighter2 = new Fighter("Mage");
$logger = new Logger();
$game = new Game($fighter1, $fighter2, $logger);
$game->startGame();
Game ne fait plus que gérer le combat (forte cohésion), et on peut remplacer Logger sans toucher à Game (faible couplage).
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Dans ce TP, vous allez refactoriser le code de notre jeu de combat pour appliquer la forte cohésion et le faible couplage.
Vous allez :
- Créer une classe
Loggerséparée avec une méthodelog. - Modifier la classe
Gamepour recevoir ses dépendances par injection de dépendances. - Instancier les objets dans
main.phpet les injecter dansGame.
Étape 1 : Créer la classe Logger
Créez un fichier Logger.php contenant une classe Logger avec une méthode publique log($message) qui affiche le message sous la forme [LOG] <message>.
class Logger {
public function log($message) {
echo "[LOG] " . $message . "\n";
}
}
Étape 2 : Modifier la classe Game
Modifiez la classe Game pour :
- Supprimer la création interne du
Logger(new Logger()dans le constructeur). - Recevoir un objet
Fighteret un objetLoggeren paramètres du constructeur. - Stocker ces objets dans des propriétés privées
$fighteret$logger. - La méthode
startGameutilise$this->logger->log(...)pour afficher les messages.
Étape 3 : Utiliser les classes dans main.php
Dans main.php, créez séparément les objets Fighter, Logger et Game, puis injectez-les :
include 'Logger.php';
include 'Fighter.php';
include 'Game.php';
$fighter = new Fighter("Guerrier");
$logger = new Logger();
$game = new Game($fighter, $logger);
$game->startGame();
Une classe = une responsabilité. Si une classe gère plusieurs sujets sans rapport (données, logs, persistance), découpez-la en plusieurs classes. Puis injectez les dépendances via le constructeur plutôt que de les créer en interne avec new. Ces 2 pratiques combinées produisent un code plus facile à tester, à modifier et à faire évoluer.
Une solution
Vous devez être connecté pour voir le contenu.