Pile étendant LinkedList. Une violation du principe de substitution de Liskov?

13

Une classe LinkedList existe avec des fonctions telles que add_first (), add_last (), add_after (), remove_first (), remove_last () et remove ()

Il existe maintenant une classe Stack qui fournit des fonctionnalités telles que push (), pop (), peek () ou top (), et pour implémenter ces méthodes, elle étend les méthodes de la classe LinkedList. Est-ce une violation du principe de substitution de Liskov?

Par exemple, considérons le cas add_after () pour ajouter un nœud à la nième position d'une liste liée. Cela peut être fait dans la classe de base mais pas dans la classe Stack. Les postconditions sont-elles affaiblies ici ou modifiez-vous la méthode add_after () pour l'ajouter en haut de la pile?

En outre, si ce n'est pas une violation, est-ce une mauvaise conception? Et comment implémenteriez-vous la fonctionnalité Stack à l'aide de la classe LinkedList?

jxvmiranda
la source
6
La question Quel serait l'inconvénient de définir une classe comme une sous-classe d'une liste d'elle-même? pose des questions sur un problème légèrement différent, mais la plupart des réponses s'appliquent ici également: il vaut mieux créer une pile avec une liste en tant que membre privé plutôt que d'hériter de la liste.
amon
7
Oui, c'est une mauvaise conception car cela vous empêchera de changer la représentation interne de la pile en autre chose qu'une liste liée (par exemple un tableau). Vous exposez également des opérations que les piles ne prennent généralement pas en charge. Utilisez la composition au lieu de l'héritage.
Lee
2
Voulez-vous prolonger LinkedList? Vous voudrez peut-être suivre les conseils d'un certain Joshua Bloch (qui a joué un rôle majeur dans la conception du cadre des collections Java) quand il a dit "Préférez la composition à l'héritage" - Peut-être avez-vous une LinkedListclasse dans votre pile, mais ne l'étendez pas réellement?
corsiKa
1
Je dirais qu'elle ne peut pas satisfaire le principe de substitution de Liskov, car "une pile est une sorte de liste chaînée" n'est pas une vraie affirmation. "Stack" est vraiment une interface (je veux dire conceptuellement, pas la construction Java). Une pile peut être implémentée avec une liste chaînée. Comme d'autres l'ont dit, vous utiliseriez un membre de données privées de type LinkedListqui fait le gros du travail dans les coulisses (composition). De cette façon, les utilisateurs de votre code ne peuvent pas utiliser accidentellement une pile là où ils avaient besoin d'une liste ou vice versa.
Jasmijn
1
Il semble que ce qui serait plus sain serait le contraire. Si je faisais cela, j'aurais probablement une classe de liste liée implémenter une interface "pile" comme étant entièrement capable de le faire. Sinon, c'est le mauvais sens car une pile est un concept, une liste chaînée est une implémentation qui peut servir de pile entre autres.
Vality

Réponses:

31

Il existe maintenant une classe Stack qui fournit des fonctionnalités telles que push (), pop (), peek () ou top (), et pour implémenter ces méthodes, elle étend les méthodes de la classe LinkedList. Est-ce une violation du principe de substitution de Liskov?

Non. Il est parfaitement possible d'ajouter des méthodes dans un sous-type.

Par exemple, considérons le cas add_after () pour ajouter un nœud à la nième position d'une liste liée. Cela peut être fait dans la classe de base mais pas dans la classe Stack.

Il s'agit d' une violation du LSP. Le LSP dit que les instances d'un sous-type doivent être substituables aux instances d'un sur-type sans changer les propriétés souhaitables d'un programme. Si un sous-type supprime une méthode, tout code qui appelle cette méthode se bloquera (ou obtiendra une NoMethodErrorexception, ou quelque chose du genre ). De toute évidence, "ne pas planter" est une propriété souhaitable.

Les postconditions sont-elles affaiblies ici ou modifiez-vous la méthode add_after () pour l'ajouter en haut de la pile?

Modifier la add_after()méthode de cette manière est une violation de la règle d'historique (la plus importante des règles!) Et n'aide donc pas à corriger la violation du LSP.

Et comment implémenteriez-vous la fonctionnalité Stack à l'aide de la classe LinkedList?

En utilisant la composition.

REMARQUE: tout ce que j'ai écrit ci-dessus s'applique uniquement aux langues qui confondent le sous-typage et le sous-classement! Le LSP concerne le sous - typage , pas le sous-classement. Dans une langue qui ne confondez pas les deux, il serait tout à fait acceptable de faire Stackune sous - classe LinkedList, aussi longtemps que vous ne faites pas un sous - type de LinkedList.

Jörg W Mittag
la source
9
Qu'est-ce que la règle d'histoire? Je ne l'ai pas trouvé sur Internet.
Zapadlo
10
Lors de la manipulation d'un objet d'un sous-type et de son observation par des méthodes du supertype, il doit être impossible d'observer une histoire qui ne pourrait pas également être observée avec un objet du supertype. Cette règle est celle qui rend le LSP applicable aux langages non fonctionnels. Toutes les autres choses étaient déjà connues bien avant cela. Les règles de pré- et post-condition sont des reformulations des règles de co- et contravariance pour les types de fonction. La règle d'historique rend tout cela applicable aux données mutables. Sans cela, le LSP ne serait utile que pour les langages purement fonctionnels.
Jörg W Mittag
2
Je pense que l'explication dans l'article Wikipedia est assez bonne: wikipedia.org/wiki/Liskov_substitution_principle Mais comme toujours, vous devriez vraiment lire la source originale.
Jörg W Mittag
@ JörgWMittag: Bien que je pense qu'il soit raisonnable que les types incluent dans leur contrat des garanties sur ce que "l'histoire" sera ou ne sera pas observée, je ne pense pas qu'il devrait être nécessaire que chaque super-type soit capable de produire chaque séquence d'observations cela pourrait être possible sur un objet du sous-type - simplement que le sur-type documente la possibilité que les consommateurs du sur-type puissent observer de telles séquences.
supercat
1

Comme tout a déjà été abordé par Jörg W Mittag, je développe un peu la partie suivante:

Les postconditions sont-elles affaiblies ici ou modifiez-vous la méthode add_after () pour l'ajouter en haut de la pile?

Fondamentalement, lorsqu'il y a une question de savoir si une hiérarchie viole LSP, cela dépend du contrat que vous posez. Alors, quel contrat add_aftera? Si cela vous semble fou, comme "Ajouter un nœud à la n-ième position ou en haut", alors ça va, la post-condition est remplie, LSP n'est pas violé. Sinon, c'est une violation LSP.

Zapadlo
la source