Le principe ouvert/fermé (OCP) en JavaScript : Extensible mais stable

Image by seth0s from Pixabay

Le principe ouvert/fermé stipule que les entités logicielles (classes, modules, fonctions, etc.) doivent être ouvertes à l’extension, mais fermées à la modification.

Voyons comment appliquer ce principe en JavaScript.

Le problème : Code difficile à étendre

Considérons une classe Calculator qui effectue différentes opérations :

class Calculator {
  calculate(num1, num2, operation) {
    if (operation === 'add') {
      return num1 + num2;
    } else if (operation === 'subtract') {
      return num1 - num2;
    }
    // Ajouter une nouvelle opération nécessiterait de modifier cette classe
  }
}

const calc = new Calculator();
console.log(calc.calculate(10, 5, 'add')); // 15
console.log(calc.calculate(10, 5, 'subtract')); // 5

Cette implémentation viole le principe ouvert/fermé car pour ajouter une nouvelle opération, nous devrions modifier la classe Calculator.

La solution : Utiliser le polymorphisme et la composition

Appliquons le principe ouvert/fermé en utilisant une stratégie de design pattern :

class Operation {
  perform(num1, num2) {
    throw new Error("La méthode perform doit être implémentée");
  }
}

class Addition extends Operation {
  perform(num1, num2) {
    return num1 + num2;
  }
}

class Subtraction extends Operation {
  perform(num1, num2) {
    return num1 - num2;
  }
}

class Calculator {
  constructor() {
    this.operations = new Map();
  }

  registerOperation(name, operation) {
    this.operations.set(name, operation);
  }

  calculate(num1, num2, operationName) {
    const operation = this.operations.get(operationName);
    if (!operation) {
      throw new Error(`Opération non supportée: ${operationName}`);
    }
    return operation.perform(num1, num2);
  }
}

// Utilisation
const calc = new Calculator();
calc.registerOperation('add', new Addition());
calc.registerOperation('subtract', new Subtraction());

console.log(calc.calculate(10, 5, 'add')); // 15
console.log(calc.calculate(10, 5, 'subtract')); // 5

// Ajouter une nouvelle opération sans modifier Calculator
class Multiplication extends Operation {
  perform(num1, num2) {
    return num1 * num2;
  }
}

calc.registerOperation('multiply', new Multiplication());
console.log(calc.calculate(10, 5, 'multiply')); // 50

Avantages de cette approche

  1. Extensibilité : Nous pouvons ajouter de nouvelles opérations sans modifier la classe Calculator.
  2. Maintainabilité : Chaque opération est encapsulée dans sa propre classe, facilitant les tests et les modifications.
  3. Flexibilité : Les opérations peuvent être ajoutées ou retirées dynamiquement.

En appliquant le principe ouvert/fermé, nous avons créé un système plus flexible et plus facile à maintenir. Notre Calculator est maintenant ouverte à l’extension (nous pouvons ajouter de nouvelles opérations) mais fermée à la modification (nous n’avons pas besoin de changer le code existant pour ajouter ces nouvelles opérations).

Kevin
Développeur fullstack depuis 16 ans, j'aime résoudre des problèmes complexes et me lancer dans des projets stimulants. J'ai développé de nombreux produits : progiciels, site e-commerce, web services, API ou encore plateforme Saas. J'aime l'accessibilité, le clean code, l'automatisation et améliorer l'Expérience Utilisateur.