Liskov Substitution Principle

Les méthodes qui utilisent des objets d'une classe
doivent pouvoir utiliser des objets dérivés de cette classe
sans même le savoir.
Liskov Substitution Principle - LSP

L'OCP repose sur la légitimité des abstractions

La présentation du principe d'ouverture/fermeture a permis de montrer le rôle de l'héritage dans la séparation du commun et du variable. Plus généralement, l'introduction de classes d'interface permet de briser des dépendances directes entre classes en faisant reposer une classe sur une abstraction d'autres classes :

Cette technique, qui joue un rôle primordial dans la modularité des applications objet, n'est cependant efficace que dans la mesure où une abstraction est réellement identifiable. En d'autres termes la classe d'interface I doit fournir une bonne abstraction des classes Ci, et à l'inverse les classes Ci doivent se conformer correctement à l'interface I. C'est précisément le sens du principe de substitution de Liskov.

Le principe de substitution de Liskov

Barbara Liskov donne la définition suivante du sous-typage :

"What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T."
Barbara Liskov, Data Abstraction and Hierarchy, SIGPLAN Notices, 23,5 (1988).

En clair, pour prétendre à l'héritage, une sous-classe doit être conçue de sorte que ses instances puissent se substituer à des instances de la classe de base partout où cette classe de base est utilisée.

L'héritage comme offre de service

Cette définition de l'héritage place l'accent sur la classe de base, qui se présente comme une veritable offre de service affichée par chaque sous-classe. En langage objet, cela revient à dire que la classe de base est une interface exportée par toutes ses sous-classes.

Ce concept rejoint celui du "Design by contract" de Bertrand Meyer, l'interface représentant un véritable contrat passé entre chaque sous-classe et les classes susceptibles de l'utiliser.

En insistant sur cette approche de l'héritage, le principe de substitution s'oppose à une pratique répandue dans laquelle l'héritage est mis en oeuvre pour factoriser du code entre plusieurs classes. Cette pratique, bien que parfaitement "légale" du point de vue du langage, représente une utilisation parfois abusive du mécanisme d'héritage à laquelle les "puristes" préfèrent l'héritage privé ou la composition.

La substitution parfaite ?

Selon ce principe, l'héritage est donc d'autant plus efficace que la substitution est parfaite. Comme toujours cela est plus facile à dire qu'à faire : tôt ou tard apparaît une sous-classe qui ne "rentre pas dans l'interface", et là deux solutions s'offrent au développeur :

  • soit il reste ferme sur l'interface et impose aux utilisateurs de la sous-classe de recourir au "downcast" pour traiter le cas particulier, ce qui entraîne une violation de l'OCP,
  • soit il élargit l'interface pour couvrir ce cas particulier, mais il impose alors aux autres sous-classes une partie d'interface qui ne leur correspond pas (typiquement sous la forme de méthodes définies dans l'interface mais dont l'implémentation restera vide dans les classes dérivées), avec cette fois-ci une violation du LSP.

Ce problème n'a pas de solution simple, mais la première solution nous semble préférable dans la mesure où l'intégrité du "contrat" est respectée. En d'autres termes, dans les cas où l'interface ne convient pas on préfèrera recourir au downcast et laisser les problèmes où ils apparaissent plutôt que de tenter de masquer le problème en corrompant l'interface.

Le LSP ne se réduit pas à l'héritage

Pour reprendre le vocabulaire employé par Jim Coplien dans son livre "Multi-Paradigm Design with C++", l'interface exprime une commonalité, et chaque sous-classe représente une variabilité particulière liée à cette commonalité. "Cope" distingue les variabilités positives, c'est-à-dire celles qui respectent la commonalité, des variabilités négatives, c'est-à-dire celles qui la rompent.

L'argument central de Coplien dans ce livre est que le traitement des commonalités et des variabilités forme le fondement du design, et que son implémentation ne se réduit pas à la mise en oeuvre du paradigme objet. Le principe de substitution s'applique également selon lui aux cas de spécialisation de templates ou de surcharge de fonctions.

_____
Article original : http://www.objectmentor.com/publications/lsp.pdf
Voir également LiskovSubstitutionPrinciple sur Wiki
et la notion de "Design by contract" sur le site d'Eiffel.