Loi de Déméter

La loi de Déméter est un sujet assez souvent abordé dans le domaine du code propre. On peut notamment avoir des infos dans Clean Code ou encore dans The Pragmatic Programmer.

C’est une loi essentielle, que vous devez connaître.

Le principe

Cette loi se concentre sur la façon dont les classes ont des dépendances entre elles.
Une méthode _m_ d’un objet _A_ peut accéder seulement aux méthodes et membres :

  • de _A_ et de ses classes de base;
  • des arguments de _m_;
  • des objets créés dans _m_;
  • des objets membres de _A_.

Cette loi impose donc que chaque unité n’ait qu’une connaissance limité des autres unités. C’est purement et simplement de l’encapsulation.

Comparaison avec les paramètres de confidentialité de Facebook

  • Moi uniquement = Portée privée
  • Mes amis = Portée publique
  • Les amis de mes amis = Portée publique
  • Tout le monde = Portée globale

Il est connu depuis longtemps qu’une variable globale est une très mauvaise pratique, ne revenons pas la dessus.

En appliquant une portée publique à une méthode (ou membre) vous permettez à vos amis de vous parler, mais vous permettez également aux amis de vos amis de vous parler. C’est la qu’intervient la loi de Déméter, elle dicte que les amis de vos amis ne sont pas autorisés à vous parler, mais ils ont le droit de vous transmettre des messages à travers vos amis communs.

L’importance de cette loi

Il ne faut pas parler aux amis de nos amis, car après nous dépendons d’eux, nous sommes couplés à eux, et lorsqu’ils changent, nous devons aussi changer. Respecter la loi de Démeter permet d’avoir un code découplé et donc plus maintenable.

Cette loi est simple à appliquer et aide à bien séparer les comportements de chaque classe.

Exemple pratique

Prenons le cas d’une école, composée de niveaux (CP, CE1, …, CM2), composée de classes. Chaque école doit être capable de fournir le nombre d’élèves inscrits.

Code sale

Prenons en compte ces simples contraintes et faisons le plus simple possible.

public class School
{
private List<Grade> _grades;

public int GetCountStudents()
{
var count = 0;
foreach (var grade in _grades)
{
foreach (var studentsClass in grade.Classes)
{
foreach (var student in studentsClass.Students)
{
count++;
}
}
}
// ou via linq : _grades.Select
// (grade => grade.Classes.Select
// (studentsClass => studentsClass.Students.Count).Sum()).Sum();
return count;
}
}

public class Grade
{
public List<StudentsClass> Classes {get; private set;}
}

public class StudentsClass
{
public List<Student> Students {get; private set;}
}

public class Student {}

On peut voir que ce code ne respecte pas la loi de Déméter, en effet une méthode de la classe School accède à une propriété de l’objet StudentsClass.

Code propre

Il est très simple de résoudre cette violation de la loi de Déméter, il suffit que les objets encapsulent leurs propriétés !

public class School
{
private List<Grade> _grades;

public int GetStudentsCount()
{
var count = 0;
foreach (var grade in _grades)
{
count += grade.GetStudentsCount();
}
return count;
}
}

public class Grade
{
public List<StudentsClass> Classes {get; private set;}

public int GetStudentsCount()
{
var count = 0;
foreach (var studentsClass in Classes)
{
count += studentsClass.GetStudentsCount();
}
return count;
}
}

public class StudentsClass
{
public List<Student> Students {get; private set;}

public int GetStudentsCount()
{
return Students.Count();
}
}

public class Student {}

Et on peut maintenant changer la façon dont sont stockées les listes dans nos objets sans aucune incidence sur les autres classes !