Ok, donc on est en effet en désaccord sur le fond vraiment, pour moi le LSP n’est pas violé dans ce cas — qui est plus simple et direct, comme tu le dis, et c’est bien pour ça que je le proposais : se mettre d’accord (ou non) sur un cas simple avant d’ajouter de la complexité.
Pour moi la propriété implicite que tu identifies (i > 0) et est brisée par la classe fille n’est qu’un détail d’implémentation et ne doit pas rentrer en compte pour le LSP.
Je vois le LSP avant tout comme un outil pratique entre deux personnes :
- Le fournisseur du type d’origine et son interface;
- L’utilisateur du type d’origine et créateur de sous-types;
L’objectif étant de laisser le plus de liberté aux deux personnes. Et c’est la qu’intervient les contrats : Le fournisseur donne un ensemble de contrats sur son interface et en échange du respect des contrats par l’utilisateur il assure une invariance des comportements dans les limites des contrats. Le LSP la dedans revient juste a dire que les contrats des interfaces des sous-types doivent être au moins aussi flexible en utilisation et au moins aussi précis sur les attentes. La conséquence est que l’utilisateur a la liberté d’ouvrir le contrat et le fournisseur la liberté de l’implémentation dans le cadre du contrat.
Ici j’ai donné en commentaire les spécifications, et de ce point de vu le sous-type les respect et le type de base assure bien un comportement correct. La propriété que tu identifies ne fait pas parti du contrat, pour moi elle ne doit pas être considérée, le faire revient a contraindre la liberté du fournisseur plus que ce qu’il t’a promis.
Et je peux pousser un peu, disons que la classe mère propose un algo foo
et ce qu’il faut en protected
pour que tu puisses refaire l’algo dans une classe fille. Le fournisseur te spécifie que l’algo doit avoir une complexité en temps au pire quadratique. Or il se trouve que son implémentation est quadratique (et que tu peux le prouver). Je ne vois vraiment pas le gain a considérer — en vertus du LSP — que tu n’as pas le droit de redéfinir cet algo dans un sous-type avec un temps logarithmique que tu aurais découvert. Et, même sans le protected
, ça interdit au fournisseur d’améliorer son algorithme !
Et dans le fond la propriété temporelle dont tu parlais au départ est de ce genre : tu la déduis des pré/post et de l’interface que tu considères comme invariante . Or cette invariance me semble être une contrainte beaucoup trop forte. C’est une propriété vrai en l’état mais qui n’est qu’une coïncidence a ne pas considérer comme une propriété (et donc idem pour les propriétés temporelles que tu déduis depuis ça). Par contre, pour le cas de départ, si le fournisseur donne la spécification explicite:
Si le compteur devient négatif il le reste
La mul
brise le LSP. Je suis donc d’accord que les pre/post sur les messages ne suffisent pas, mais je maintiens que le cas présenté ici ne brise pas nécessairement le LSP.
Pour résumer, je pense comprendre ce point de vue, mais cette vision du LSP est pour moi beaucoup trop restrictive et plus néfaste qu’utile pour la conception d’un programme. Si j’accepte cette définition du LSP, je suis d’accord qu’il est violé dans les cas qu’on a considéré. Mais je trouve cette définition inutile en pratique.
D’ailleurs c’est aussi ce que fait le toy-model que j’ai présente plus haut, il faudrait que je rajoute explicitement les post-conditions quelque-part.
Directement, le fournisseur de la classe de base ne peut pas rajouter mul
dans une future itération. Ou indirectement, il n’y a rien de protected
qui permet d’étendre l’interface.