Principe de développement orienté-objet SOLID

L’acronyme signifie:
Single responsability principle (SRP°: responsabilité unique,
Open/close principle (OCP): principe ouvert/fermé,
Liskov substitution principle (LSP): substitution de Liskov,
Interface segragation principle (ISP): ségrégation des interfaces,
Dependency inversion principle (DIP): inversion des dépendances.

Responsabilité unique

Une classe doit avoir une et seulement une seule raison de changer.

Principe ouvert/fermé

"Les entités logicielles (classes, modules, fonctions…) doivent être ouvertes aux extensions et fermées aux modifications" (Bertrand Meyer, Object-Oriented Software Construction, 1988).

Il doit être possible de modifier le code d’une classe pour résoudre des défauts cependant on doit pouvoir étendre les fonctionnalités d’une classe sans la modifier. Ce principe permet d’aider à garder un code maintenable et testable car le comportement actuel ne doit pas changer, les nouveaux comportements existent dans de nouvelles classes. En suivant le principe ouvert/fermé, il est ainsi possible d’ajouter des fonctionnalités transverses à une application.
Par exemple, si on souhaite ajouter la fonctionnalité de "logging" à un ensemble de classes, on ne doit pas changer l’implémentation des classes existantes.

Substitution de Liskov

Ce principe indique que: si S est un sous-type de T alors les objets de type T peuvent être remplacés par des objets de type S sans altérer les propriétés ni l’exactitude du programme.

Une façon de répondre à ce principe est d’utiliser des interfaces ou des classes abstraites. Ainsi si on définit le constructeur d’une classe avec un objet définit par son interface, il est possible de remplacer l’objet injecté pourvu qu’il satisfasse toujours l’interface:

public interface IShape
{
  void Draw();
}

public class ShapeDrawHandler
{
  private IShape shape;
  public ShapeDrawHandler(IShape shape)
  {
    this.shape = shape;
  }

  public void DrawShape()
  {
    this.shape.Draw();
  }
}

Quelque soit la forme géométrique IShape, elle pourra être dessinée par ShapeDrawHandler si elle satisfait l’interface IShape.

Ségrégation des interfaces

Ce principe a pour but de rendre un logiciel plus maintenable. Le principe de ségrégation des interfaces encourage le faible couplage et ainsi, rend un système plus facile à refactorer, changer et redéployer.
Ce principe indique qu’une interface très grande devrait être séparée en plusieurs interfaces plus petites et plus spécifiques de façon à ce que les classes consommatrices de ces interfaces connaissent seulement les méthodes qu’elles utilisent: aucune classe consommatrice d’une interface ne devrait être forcée à dépendre de méthodes qu’elle n’utilise pas.

Par exemple:

public interface IItemInitializer
{
  void InitializeItem();
  Tenant GetCreateItem(string itemId);
  IEnumerable GetItems();
}

public interface IItemUpdater
{
  void SaveItem(Item item);
}


public class ItemStore : IItemInitializer, IItemUpdater
{
  ...

  public ItemStore()
  {
    ...
  }

  ...
}

Dans ce cas, on a séparé la fonctionnalité d’initialisation de la fonctionnalité de mise à jour.

Inversion des dépendances

Ce principe indique que:
– Les modules de haut niveau ne doivent pas dépendre des modules de bas niveau. Les deux doivent dépendre d’abstractions.
– Les abstractions ne doivent pas dépendre des détails. Les détails doivent dépendre des abstractions.

Par exemple si on considère l’exemple suivant:

public class Square
{
  public void Draw()
  {...}
}

public class ShapeDrawer
{
  private Square shape;
  public ShapeDrawer()
  {
    this.shape = new Square();
  }

  public void DrawShape()
  {
    this.shape.Draw();
  }
}

La classe générale ShapeDrawer dépend d’une classe plus spécifique Square, ce qui limite la possibilité de réutiliser ShapeDrawer dans un cadre plus générale.
La modification suivante permet de casser la dépendance entre ShapeDrawer et Square:

public interface IShape
{
  void Draw();	
}

public class Square : IShape
{
  public void Draw()
  {...}
}

public class ShapeDrawer
{
  private IShape shape;
  public ShapeDrawer(IShape shape)
  {
    this.shape = shape;
  }

  public void DrawShape()
  {
    this.shape.Draw();
  }
}

Leave a Reply