Cette classe est-elle un sous type légitime ?

selon le principe de substitution de Liskov

a marqué ce sujet comme résolu.

Est-ce que dans cette situation le LSP est violé ?

Freedom

(Je ne prétends pas avoir la réponse au problème posé, simplement que tous les éléments temporels sont plus subtils qu’il n’y paraissent à mon avis et que si on autorise le mot clé protected/public le respect des contraintes sur les pré et post-conditions me semble léger pour maintenir des propriétés temporelles. C’est d’ailleurs pour ça que je trouve qu’il est généralement une mauvaise pratique d’avoir des propriétés temporelles sur les objets pour les appels de fonctions membres, et encore plus quand on autorise la dérivation).

Est-ce que dans cette situation le LSP est violé ?

Freedom

C’est effectivement aussi une violation, plus directe, mais également due à une propriété cachée (implicitement foo assure i positif, mais sa surcharge non). Et en fait, ce qui est intéressant une nouvelle fois c’est que le problème ne va apparaître que si le développeur a fait des suppositions supplémentaires sur le programme.

Ici on peut raisonnablement considérer que les contrats sont imprécis. Mais sur les propriétés temporelles, on ne peut pas spécifier si simplement ce qui rend ce genre d’imprécisions encore mieux planquées. Il y a une différence qui est assez importante entre cet exemple et le premier. On ne peut pas si facilement expliciter une propriété temporelle et il est encore plus difficile de la vérifier.

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éduis1 des pré/post et de l’interface que tu considères comme invariante 2. 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.

1 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.
2 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.

+0 -0

Ah mais attention ! Je ne dis pas que la situation que je décris est souhaitable, ni que le développeur prend nécessairement en compte ces infos. Mais à force de faire de la preuve, ce dont je me rends compte, c’est que justement ces situations d’imprécision sont très dangereuses.

Typiquement, on pourrait avoir une situation où une propriété implicite d’un objet est absolument nécessaire à un programme sans pour autant que cela soit voulu explicitement par le développeur. Et pour les propriétés temporelles c’est affreux, parce que ça peut vraiment se planquer. Et le jour où on ajoute un nouveau type censément correct, parce qu’il ne respecte pas une propriété implicite, il fait exploser du code existant. Et à mon sens, le problème est bien là.

Ah mais attention ! Je ne dis pas que la situation que je décris est souhaitable, ni que le développeur prend nécessairement en compte ces infos

Le départ de la discussion c’était quand même de savoir quand il est souhaitable de considérer qu’un type est un sous-type viable, si tu arrives a une situation ou tu rejettes un sous-type tout en disant que le raisonnement ayant conduit au rejet n’est pas souhaitable, je trouve ça un peu paradoxale :) .

Typiquement, on pourrait avoir une situation où une propriété implicite d’un objet est absolument nécessaire à un programme sans pour autant que cela soit voulu explicitement par le développeur.

Lorsque cette situation arrive 1, tu tires quelles conclusions/recommandations ? Pour moi le programme a une mauvaise conception 2.

Et le jour où on ajoute un nouveau type censément correct, parce qu’il ne respecte pas une propriété implicite, il fait exploser du code existant. Et à mon sens, le problème est bien là.

Certes, sauf que dans ce cas j’incrimine plus le code existant : il explose uniquement parce-qu’il se base sur une propriété implicite. Or, idéalement, c’est en amont que cette utilisation d’une propriété implicite aurait du être identifiée et le code rejeté (donc bien en amont de toute substitution 3).

Je ne dis pas que le problème que tu exposes n’en est pas un, loin de la, simplement que — pour moi — ce n’est pas le sens du LSP.

1 Je ne dis pas que c’est simple d’identifier ou de prouver de telles propriétés (ça je te laisse le soin de juger^^).

2 Sans que ce soit le LSP qui rentre nécessairement en jeu.

3 Et c’est une façon de voir que ce n’est pas le LSP qui est pris a défaut : le problème de conception est la avant la substitution, i.e. le code a déjà "explose" avant même que l’idée d’une substitution ne germe.

Le départ de la discussion c’était quand même de savoir quand il est souhaitable de considérer qu’un type est un sous-type viable, si tu arrives a une situation ou tu rejettes un sous-type tout en disant que le raisonnement ayant conduit au rejet n’est pas souhaitable, je trouve ça un peu paradoxale :) .

Freedom

Bah elle est pas souhaitable mais malheureusement, on doit faire avec. Les propriétés temporelles c’est pénible à modéliser et en plus on a des types qui en usent pas mal (par exemple les types fichiers qui demandent à ce que le fichier soit ouvert avant de pouvoir lire, pour prendre un cas super trivial).

Lorsque cette situation arrive 1, tu tires quelles conclusions/recommandations ? Pour moi le programme a une mauvaise conception 2.

Freedom

J’ai pas de réponses. Il n’est pas si simple de dire que le système est mal conçu. Ou alors pour mal conçu, on donne l’idée "quand on n’a pas fait une preuve, c’est mal conçu" :P

Je me trompe où cette propriété temporelle est un invariant ponctuel? C’est à dire que tu exiges parfois, mais pas toujours?

lmghs

Au niveau de l’objet qui a une propriété temporelle, ce n’est pas tant une exigence qu’une conséquence de tes exigences et des actions que tu effectues sur l’objet.

Je ne sais pas si on peut parler d’invariant ponctuel. En particulier, si je me souviens bien (mais là pour le coup, je ne suis pas du tout expert sur la logique temporelle), il y a des propriétés que tu ne peux pas si facilement exprimer hors de la logique temporelle, notamment parce que ça fait intervenir des propriétés existentielles potentiellement assez méchantes qui parlent de ton chemin d’exécution.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte