Un sublcass no debería alterar el comportamiento del padre?
Esa es una mala interpretación común de LSP. Una subclase puede alterar el comportamiento del padre, siempre que permanezca fiel al tipo padre.
Hay una buena explicación larga en Wikipedia , que sugiere las cosas que infringirán LSP:
... hay una serie de condiciones de comportamiento que el subtipo debe cumplir. Estos se detallan en una terminología similar a la del diseño por metodología de contrato, lo que lleva a algunas restricciones sobre cómo los contratos pueden interactuar con la herencia:
- Las condiciones previas no pueden fortalecerse en un subtipo.
- Las condiciones posteriores no pueden debilitarse en un subtipo.
- Las invariantes del supertipo deben conservarse en un subtipo.
- Restricción del historial (la "regla del historial"). Los objetos se consideran modificables solo a través de sus métodos (encapsulación). Dado que los subtipos pueden introducir métodos que no están presentes en el supertipo, la introducción de estos métodos puede permitir cambios de estado en el subtipo que no están permitidos en el supertipo. La restricción de la historia lo prohíbe. Fue el nuevo elemento introducido por Liskov y Wing. Una violación de esta restricción puede ejemplificarse definiendo un MutablePoint como un subtipo de un ImmutablePoint. Esto es una violación de la restricción del historial, porque en el historial del punto Inmutable, el estado es siempre el mismo después de la creación, por lo que no puede incluir el historial de un MutablePoint en general. Sin embargo, los campos agregados al subtipo pueden modificarse de manera segura porque no son observables a través de los métodos de supertipo.
Personalmente, me resulta más fácil simplemente recordar esto: si estoy mirando un parámetro en un método que tiene el tipo A, ¿alguien que pase un subtipo B me causaría alguna sorpresa? Si lo hicieran, entonces hay una violación de LSP.
¿Lanzar una excepción es una sorpresa? Realmente no. Es algo que puede suceder en cualquier momento, ya sea que llame al método de envío en OrderState o Granted o Shipped. Así que tengo que dar cuenta de ello y no es realmente una violación de LSP.
Dicho esto , creo que hay mejores formas de manejar esta situación. Si escribiera esto en C #, usaría interfaces y verificaría la implementación de una interfaz antes de llamar al método. Por ejemplo, si el OrderState actual no implementa IShippable, no llame a un método de envío.
Pero entonces tampoco usaría el patrón de Estado para esta situación particular. El patrón de estado es mucho más apropiado para el estado de una aplicación que para el estado de un objeto de dominio como este.
En pocas palabras, este es un ejemplo mal diseñado del Patrón de Estado y no es una forma particularmente buena de manejar el estado de una orden. Pero podría decirse que no infringe LSP. Y el patrón del Estado, en sí mismo, ciertamente no lo hace.