Pila que extiende LinkedList. ¿Una violación del principio de sustitución de Liskov?

13

Existe una clase LinkedList con funciones como add_first (), add_last (), add_after (), remove_first (), remove_last () y remove ()

Ahora hay una pila de clases que proporciona funcionalidades como push (), pop (), peek () o top (), y para implementar estos métodos, extiende los métodos de la clase LinkedList. ¿Es esto una violación del Principio de sustitución de Liskov?

Por ejemplo, considere el caso add_after () para agregar un nodo en la enésima posición de una Lista vinculada. Esto se puede hacer en la clase base pero no en la clase Stack. ¿Se están debilitando las condiciones posteriores aquí o modifica el método add_after () para agregar a la parte superior de la Pila?

Además, si no es una violación, ¿es este un mal diseño? ¿Y cómo implementaría la funcionalidad Stack usando la clase LinkedList?

jxvmiranda
fuente
66
La pregunta ¿Cuál sería la desventaja de definir una clase como una subclase de una lista de sí misma? pregunta sobre un problema ligeramente diferente, pero la mayoría de las respuestas también se aplican aquí: es mejor crear una Pila con una Lista como miembro privado en lugar de heredar de Lista.
amon
77
Sí, este es un mal diseño, ya que evitará que cambie la representación interna de la pila a otra que no sea una lista vinculada (por ejemplo, una matriz). También está exponiendo operaciones que las pilas no suelen admitir. Use composición en lugar de herencia.
Lee
2
¿Quieres ampliar LinkedList? Es posible que desee prestar atención a los consejos de cierto Joshua Bloch (que jugó un papel importante en el diseño del marco de colecciones de Java) cuando dijo "Prefiera la composición sobre la herencia" - ¿Quizás tenga una LinkedListclase dentro de su pila, pero en realidad no la extienda?
corsiKa
1
Yo diría que no puede satisfacer el principio de sustitución de Liskov, porque "una pila es una especie de lista vinculada" no es una declaración verdadera. "Pila" es realmente una interfaz (quiero decir conceptualmente, no la construcción Java). Se puede implementar una pila con una lista vinculada. Como otros han dicho, usaría un miembro de datos privado del tipo LinkedListque hace el trabajo pesado detrás de escena (composición). De esa forma, los usuarios de su código no pueden usar accidentalmente una Pila donde necesitaban una Lista o viceversa.
Jasmijn
1
Parece que lo más sensato sería lo contrario. Si estuviera haciendo esto, probablemente haría que una clase de lista vinculada implementara una interfaz "stack", ya que es totalmente capaz de hacerlo. De lo contrario, este es el camino equivocado, ya que una pila es un concepto, una lista vinculada es una implementación que puede actuar como una pila, entre otras cosas.
Vality

Respuestas:

31

Ahora hay una pila de clases que proporciona funcionalidades como push (), pop (), peek () o top (), y para implementar estos métodos, extiende los métodos de la clase LinkedList. ¿Es esto una violación del Principio de sustitución de Liskov?

No. Está perfectamente bien agregar métodos en un subtipo.

Por ejemplo, considere el caso add_after () para agregar un nodo en la enésima posición de una Lista vinculada. Esto se puede hacer en la clase base pero no en la clase Stack.

Esto es una violación del LSP. El LSP dice que las instancias de un subtipo deben ser sustituibles por instancias de un supertipo sin cambiar las propiedades deseables de un programa. Si un subtipo elimina un método, cualquier código que llame a ese método se bloqueará (u obtendrá una NoMethodErrorexcepción, o algo por el estilo). Claramente, "no estrellarse" es una propiedad deseable.

¿Se están debilitando las condiciones posteriores aquí o modifica el método add_after () para agregar a la parte superior de la Pila?

Modificar el add_after()método de esta manera es una violación de la Regla de Historia (¡la más importante de las reglas!) Y, por lo tanto, no ayuda a corregir la violación del LSP.

¿Y cómo implementaría la funcionalidad Stack usando la clase LinkedList?

Mediante el uso de la composición.

NOTA: ¡Todo lo que escribí arriba solo se aplica a los idiomas que confunden los subtipos y las subclases! El LSP se trata de subtipar , no de subclasificar. En un lenguaje que no confunda los dos, sería perfectamente aceptable hacer Stackuna subclase de LinkedList, siempre que no lo conviertas en un subtipo de LinkedList.

Jörg W Mittag
fuente
99
¿Cuál es la regla de la historia? No pude encontrarlo en internet.
Zapadlo
10
Al manipular un objeto de un subtipo y observarlo a través de métodos del supertipo, debe ser imposible observar una historia que no pueda observarse también con un objeto del supertipo. Esta regla es la que hace que el LSP sea aplicable a lenguajes no funcionales. Todo lo demás ya se conocía mucho antes. Las reglas previas y posteriores a la condición son reformulaciones de las reglas de covarianza y contravarianza para los tipos de función. La regla del historial hace que todo esto sea aplicable a los datos mutables. Sin él, el LSP solo sería útil para lenguajes puramente funcionales.
Jörg W Mittag
2
Creo que la explicación en el artículo de Wikipedia es razonablemente buena: wikipedia.org/wiki/Liskov_substitution_principle Pero como siempre, realmente debería leer la fuente original.
Jörg W Mittag
@ JörgWMittag: Aunque creo que es razonable que los tipos incluyan en sus contratos garantías sobre qué "historia" se observará o no, no creo que sea necesario que cada supertipo sea capaz de producir cada secuencia de observaciones eso podría ser posible en un objeto del subtipo, simplemente que el supertipo documente la posibilidad de que los consumidores del supertipo puedan observar tales secuencias.
supercat
1

Como todo ya fue abordado por Jörg W Mittag, acabo de explicar un poco la siguiente parte:

¿Se están debilitando las condiciones posteriores aquí o modifica el método add_after () para agregar a la parte superior de la Pila?

Básicamente, cuando hay una pregunta sobre si alguna jerarquía viola el LSP, depende del contrato que usted presente. Entonces, ¿qué contrato add_aftertiene? Si suena, por loco que sea, como "Agregar un nodo en la enésima posición o en la parte superior", entonces está bien, se cumple la condición posterior, no se viola el LSP. De lo contrario, es una violación de LSP.

Zapadlo
fuente