SOLID : guide pour débutant en Java (Partie 2)

SOLID : guide pour débutant en Java (Partie 2)

Open/Closed Principle 🔒

Dans la deuxième partie de cette note, nous allons aborder le principe Open/Closed dans le cadre du principe SOLID. Avant d'entamer cette discussion, je vous encourage à revoir la première partie: https://josue.hashnode.dev/solid-guide-pour-debutant-en-java-partie-1

Open/Closed Principle 🔒

Comme on le dit souvant :

Les entités doivent être ouvertes à l'extension et fermées à la modification.

Cela signifie en français facile que l’on doit toujours favoriser l’extension du code à sa modification on ne modifie pas le fonctionnement suivant l’entité à utiliser, on définit une fonction commune. (ne me remercie pas pour cette brillante explication 🧘🏿‍♂️)

Plus sérieusement la plupart des illustrations du principe d'ouverture/fermeture que l'on trouve sur Internet se concentrent souvent sur des objets ou des entités. Cependant, ce concept s'applique tout autant aux services, modules, fonctions, classes, et ainsi de suite.

Dans cet exemple, je choisis d'afficher un message de bienvenue à l'utilisateur, peu importe son type. L'important est que mon action reste inchangée pour chaque élément, même si la manière dont le message est affiché varie.

Voici un exemple de code qui ne suit pas le principe de Open/Closed Principle 🔒

public class User {
   public String name;
   public String firstname;
   public User(String firstname, String name)
    {
        firstname = firstname;
        name = name;
    }
}
  public class Customer {
    public String fullname;

    public Customer(String fullname)
    {
        fullname = fullname;
    }
}
public class AccountDisplayerService
{
    public void displayWelcomeMessage(Object entity)
    {
        if (entity instanceof User) {
            System.out.printf("Hello, %s %s", entity.name,entity.firstname);

        } else if (entity instanceof Customer) {
            System.out.printf("Welcome again, dear %s\n", entity.fullname);
        }
    }
}
public class Test {

    public static void main(String[] args){

       User user = new User('Kouakou', 'Zokou');
       Customer customer = new Customer('Mr Zokou Armel');

       AccountDisplayerService accountDisplayer = new AccountDisplayerService();
       accountDisplayer.displayWelcomeMessage(user);
       // Hello Zokou Kouakou
       accountDisplayer.displayWelcomeMessage(customer);
       // Welcome again, dear Mr Zokou Armel

    }
}

Si je souhaite ajouter un nouveau type d'utilisateur, comme un affilié, et que cela nécessite de modifier le service qui gère l'action, cela signifie que notre système ne respecte pas pleinement le principe d'ouverture/fermeture.

Idéalement, selon le principe d'ouverture/fermeture, l'ajout d'un nouveau type d'utilisateur ne devrait pas nécessiter de modifications dans le service existant. Au lieu de cela, nous devrions pouvoir étendre le système pour prendre en charge le nouvel utilisateur sans toucher au code existant. Cela peut être réalisé en utilisant des concepts de programmation orientée objet tels que l'héritage, les interfaces ou la composition.

Dans notre cas, cela signifie que nous devons revoir la conception de votre service afin de le rendre plus extensible et de permettre l'ajout de nouveaux types d'utilisateurs sans avoir à modifier le service lui-même.

Voici un exemple de code qui suit le principe de Open/Closed Principle 🔒

public interface NameableInterface {
   void theName();
}
public class User implements NameableInterface {
   public String name;
   public String firstname;
   public User(String firstname, string name)
    {
        firstname = firstname;
        name = name;
    }

  @Override
   public void theName() {
     System.out.printf("Hello, %s %s", name,firstname);
   }
}
 public class Customer implements NameableInterface {
    String fullname;

    public Customer(String fullname)
    {
        fullname = fullname;
    }
   @Override
    public void theName() {
     System.out.printf("Welcome again, dear %s\n", entity.fullname);
   }
}
public class AccountDisplayerService
{
    public void displayWelcomeMessage(NameableInterface entity)
    {
        entity.theName()
    }
}
public class Test {

    public static void main(String[] args){

        User user = new User('Kouakou', 'Zokou');
       Customer customer = new Customer('Mr Zokou Armel');

       AccountDisplayerService accountDisplayer = new AccountDisplayerService();
       accountDisplayer.displayWelcomeMessage(user);
       accountDisplayer.displayWelcomeMessage(customer);

    }
}

Voici comment nous avons rendu notre code conforme.

🔓 Ouvert pour l’extension : c’est le cas ci-dessus, on peut étendre le comportement d’une méthode (ici theName()), chaque classe qui l’implémente fait ce qu’elle veut dans sa méthode.

🔒 Fermé à la modification : on ne change pas le code source de l’action en fonction du paramètre reçu (que j’ai un client, un utilisateur, un affilié, un administrateur ou que sais-je, le code de l’action ne sera pas modifié).

C'est une excellente pratique que nous avons mise en place en utilisant une interface pour déterminer l'action dans le service. Grâce à cela, nous avons établi un contrat clair entre le service et les objets qui sont affichés. Cela signifie que nous pouvons ajouter de nouvelles entités ou objets qui respectent le même contrat (implémentent la même interface) sans avoir à modifier le service lui-même. Cette approche est conforme au principe d'ouverture/fermeture et favorise la flexibilité et la maintenabilité de votre code.

« Tu veux que je t’affiche ? Pas de problème, implémente juste l’interface NameableInterface »

Partie 3 bientôt disponible