Dois-je bien comprendre que le principe de substitution de Liskov ne peut pas être observé dans des langues où les objets peuvent s’inspecter eux-mêmes, comme ce qui est habituel dans les langues de type canard?
Par exemple, dans Ruby, si une classe B
hérite d'une classe A
, alors pour chaque objet x
de A
, x.class
va revenir A
, mais si x
est un objet de B
, x.class
ne va pas revenir A
.
Voici une déclaration de LSP:
Que q (x) soit une propriété démontrable sur les objets x de type T . Ensuite , q (y) doit être prouvable pour les objets y de type S , où S est un sous - type de T .
Ainsi, en Ruby, par exemple,
class T; end
class S < T; end
violer LSP sous cette forme, comme en témoigne la propriété q (x) =x.class.name == 'T'
Une addition. Si la réponse est "oui" (LSP incompatible avec l'introspection), alors mon autre question serait: existe-t-il une forme modifiée "faible" de LSP qui peut éventuellement tenir pour un langage dynamique, éventuellement sous certaines conditions supplémentaires et avec seulement des types spéciaux des propriétés .
Mettre à jour. Pour référence, voici une autre formulation de LSP que j'ai trouvée sur le web:
Les fonctions qui utilisent des pointeurs ou des références à des classes de base doivent pouvoir utiliser des objets de classes dérivées sans le savoir.
Et un autre:
Si S est un sous-type déclaré de T, les objets de type S devraient se comporter comme les objets de type T devraient se comporter, s'ils sont traités comme des objets de type T.
Le dernier est annoté avec:
Notez que le LSP concerne le comportement attendu des objets. On ne peut suivre le LSP que si l'on sait clairement quel est le comportement attendu des objets.
Cela semble plus faible que l'original et pourrait être possible à observer, mais j'aimerais le voir officialisé, en particulier expliqué qui décide du comportement attendu.
Le LSP n'est-il donc pas une propriété d'une paire de classes dans un langage de programmation, mais d'une paire de classes avec un ensemble donné de propriétés, satisfaites par la classe ancêtre? Concrètement, cela signifierait-il que pour construire une sous-classe (classe descendante) respectant LSP, toutes les utilisations possibles de la classe ancêtre doivent être connues? Selon LSP, la classe ancêtre est censée être remplaçable par n'importe quelle classe descendante, non?
Mettre à jour. J'ai déjà accepté la réponse, mais je voudrais ajouter un autre exemple concret de Ruby pour illustrer la question. En Ruby, chaque classe est un module dans le sens où la Class
classe est un descendant de Module
classe. Pourtant:
class C; end
C.is_a?(Module) # => true
C.class # => Class
Class.superclass # => Module
module M; end
M.class # => Module
o = Object.new
o.extend(M) # ok
o.extend(C) # => TypeError: wrong argument type Class (expected Module)
Réponses:
Voici le principe réel :
Et l'excellent résumé de wikipedia :
Et quelques citations pertinentes du document:
Passons donc à la question:
Non.
A.class
renvoie une classe.B.class
renvoie une classe.Étant donné que vous pouvez effectuer le même appel sur le type le plus spécifique et obtenir un résultat compatible, LSP est valide. Le problème est qu'avec les langages dynamiques, vous pouvez toujours appeler des choses sur le résultat en vous attendant à ce qu'elles soient là.
Mais considérons un langage typé structurellement (canard). Dans ce cas,
A.class
retournerait un type avec une contrainte qu'il doit êtreA
ou un sous-type deA
. Ceci fournit la garantie statique que tout sous - type deA
doit fournir une méthodeT.class
dont le résultat est un type qui satisfait cette contrainte.Cela fournit une affirmation plus forte que LSP détient dans les langues qui prennent en charge la frappe de canard, et que toute violation de LSP dans quelque chose comme Ruby se produit davantage en raison d'une mauvaise utilisation dynamique normale que d'une incompatibilité de conception de langue.
la source
fail unless x.foo == 42
et qu'un sous-type renvoie 0, c'est la même chose. Ce n'est pas un échec de LSP, c'est le fonctionnement normal de votre programme. Le polymorphisme n'est pas une violation de LSP.Dans le contexte du LSP, une "propriété" est quelque chose qui peut être observé sur un type (ou un objet). Il s'agit en particulier d'une "propriété prouvable".
Une telle "propriété" pourrait exister une
foo()
méthode qui n'a pas de valeur de retour (et suit le contrat fixé dans sa documentation).Assurez-vous de ne pas confondre ce terme avec "propriété" car dans "
class
est une propriété de chaque objet dans Ruby". Une telle "propriété" peut être une "propriété LSP", mais ce n'est pas automatiquement la même chose!Maintenant, la réponse à vos questions dépend beaucoup de la rigueur avec laquelle vous définissez la «propriété». Si vous dites « la propriété de la classe
A
est que.class
renverra le type de l'objet », alorsB
effectivement fait avoir cette propriété.Si, cependant, vous définissez la "propriété" comme "
.class
retoursA
", alors évidemment, elleB
n'a pas cette propriété.Cependant, la deuxième définition n'est pas très utile, car vous avez essentiellement trouvé un moyen détourné de déclarer une constante.
la source
.class
va retourner le type de l'objet". Si cela signifie quex.class == x.class
ce n'est pas une propriété intéressante.Si je comprends bien, il n'y a rien sur l'introspection qui serait incompatible avec le LSP. Fondamentalement, tant qu'un objet prend en charge les mêmes méthodes qu'un autre, les deux doivent être interchangeables. Autrement dit, si votre code attend un
Address
objet, il n'a pas d' importance si elle est unCustomerAddress
ou uneWarehouseAddress
, tant que les deux fournissent (par exemple)getStreetAddress()
,getCityName()
,getRegion()
etgetPostalCode()
. Vous pouvez certainement créer une sorte de décorateur qui prend un type d'objet différent et utilise l'introspection pour fournir les méthodes requises (par exemple, uneDestinationAddress
classe qui prend unShipment
objet et présente l'adresse de livraison en tant queAddress
), mais ce n'est pas obligatoire et certainement pas 'empêche pas le LSP d'être appliqué.la source
x.class.name
avec'A'
, ce qui lex.class.name
rendrait inutile .x.class.name == 'A'
y a un anti-modèle dans la frappe de canard: après tout, la frappe de canard vient de "Si elle se casse et marche comme un canard, c'est un canard". Donc, s'il se comporteA
et respecte les contrats qui ontA
été proposés, c'est unA
exempleAprès avoir parcouru l'article original de Barbara Liskov, j'ai trouvé comment compléter la définition de Wikipedia afin que LSP puisse en effet être satisfait dans presque toutes les langues.
Tout d'abord, le mot "prouvable" est important dans la définition. Elle n'est pas expliquée dans l'article de Wikipédia, et les "contraintes" sont mentionnées ailleurs sans s'y référer.
Voici la première citation importante de l'article:
Et voici le second, expliquant ce qu'est une spécification de type :
Ainsi, LSP n'a de sens que par rapport à des spécifications de type données , et pour une spécification de type appropriée (pour la vide par exemple), il peut être satisfait dans probablement n'importe quelle langue.
Je considère que la réponse de Telastyn est la plus proche de ce que je cherchais car les "contraintes" étaient explicitement mentionnées.
la source
x.class.name = 'A'
est prouvable pour toute lax
classeA
si vous autorisez trop de connaissances. La spécification de type n'était pas définie et sa relation exacte avec le LSP ne l'était pas non plus, bien que de manière informelle certaines indications aient été données. J'ai déjà trouvé ce que je cherchais dans l'article de Liskov et j'ai répondu à ma question ci-dessus.x
de ces types,x.woozle
donneraundefined
, alors aucun type pour lequelx.woozle
ne donneundefined
ne sera un sous-type approprié. Si le supertype ne documente rienx.woozle
, le fait que l'utilisationx.woozle
sur le supertype produiraundefined
ne signifierait rien à ce qui pourrait faire sur le sous-type.Pour citer l'article de Wikipedia sur LSP , «la substituabilité est un principe de la programmation orientée objet». C'est un principe et une partie de la conception de votre programme. Si vous écrivez du code qui en dépend
x.class == A
, c'est votre code qui viole le LSP. Notez que ce type de code cassé est également possible en Java, aucun type de canard n'est nécessaire.Rien dans le typage canard ne brise intrinsèquement LSP. Seulement si vous en abusez, comme dans votre exemple.
Réflexion supplémentaire: la vérification explicite de la classe d'un objet ne contredit-elle pas le but de la saisie de canard, de toute façon?
la source
Liskov's notion of a behavioral subtype defines a notion of substitutability for mutable objects; that is, if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness).
. La classe exacte d'un objet n'est PAS l'une des "propriétés souhaitables du programme"; sinon, cela irait à l'encontre non seulement de la frappe de canard mais du sous-typage en général, y compris la saveur de Java.x.class == A
viole à la fois le LSP et le typage canard. Cela n'a aucun sens d'utiliser la saisie de canard si vous voulez vérifier les types réels.A
,B
) satisfait LSP ou non. Si LSP dépend du code utilisé ailleurs, il n'est pas expliqué quel code est autorisé. J'espère trouver quelque chose ici: cse.ohio-state.edu/~neelam/courses/788/lwb.pdfLet q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T
. Il est évident que cex.class
n'est pas une des propriétés intéressantes ici; sinon le polymorphisme de Java ne fonctionnerait pas non plus. Il n'y a rien d' inhérent à la saisie de canard dans votre "problème x.class". Êtes-vous d'accord jusqu'à présent?