une sous-classe ne devrait pas modifier le comportement du parent?
C'est une mauvaise interprétation courante de LSP. Une sous-classe peut modifier le comportement du parent, tant qu'elle reste fidèle au type de parent.
Il y a une bonne longue explication sur Wikipedia , suggérant les choses qui violeront le LSP:
... il existe un certain nombre de conditions comportementales que le sous-type doit remplir. Celles-ci sont détaillées dans une terminologie ressemblant à celle de la méthodologie de conception par contrat, conduisant à certaines restrictions sur la façon dont les contrats peuvent interagir avec l'héritage:
- Les conditions préalables ne peuvent pas être renforcées dans un sous-type.
- Les post-conditions ne peuvent pas être affaiblies dans un sous-type.
- Les invariants du supertype doivent être conservés dans un sous-type.
- Contrainte d'historique (la "règle d'historique"). Les objets ne sont considérés comme modifiables que par leurs méthodes (encapsulation). Étant donné que les sous-types peuvent introduire des méthodes qui ne sont pas présentes dans le supertype, l'introduction de ces méthodes peut permettre des changements d'état dans le sous-type qui ne sont pas autorisés dans le supertype. La contrainte d'historique l'interdit. C'était l'élément roman introduit par Liskov et Wing. Une violation de cette contrainte peut être illustrée en définissant un MutablePoint comme un sous-type d'un ImmutablePoint. Il s'agit d'une violation de la contrainte d'historique, car dans l'historique du point immuable, l'état est toujours le même après la création, il ne peut donc pas inclure l'historique d'un MutablePoint en général. Les champs ajoutés au sous-type peuvent cependant être modifiés en toute sécurité car ils ne sont pas observables via les méthodes de supertype.
Personnellement, je trouve plus facile de simplement me souvenir de ceci: si je regarde un paramètre dans une méthode qui a le type A, est-ce que quelqu'un qui passe un sous-type B me surprendra? S'ils le faisaient, il y aurait alors violation du LSP.
Lancer une exception est-il une surprise? Pas vraiment. C'est quelque chose qui peut arriver à tout moment, que j'appelle la méthode Ship sur OrderState ou Granted ou Shipped. Je dois donc en tenir compte et ce n'est pas vraiment une violation de LSP.
Cela dit , je pense qu'il existe de meilleures façons de gérer cette situation. Si j'écrivais cela en C #, j'utiliserais des interfaces et vérifierais l'implémentation d'une interface avant d'appeler la méthode. Par exemple, si le OrderState actuel n'implémente pas IShippable, n'appelez pas de méthode Ship dessus.
Mais je n'utiliserais pas non plus le modèle d'État pour cette situation particulière. Le modèle d'état est beaucoup plus approprié à l'état d'une application qu'à l'état d'un objet de domaine comme celui-ci.
Donc, en un mot, c'est un exemple mal conçu du modèle d'état et pas un moyen particulièrement bon de gérer l'état d'une commande. Mais cela ne contrevient sans doute pas au LSP. Et le modèle d'État, en soi, ne l'est certainement pas.