una sottoclasse non dovrebbe alterare il comportamento del genitore?
Questa è una comune interpretazione errata di LSP. Una sottoclasse può modificare il comportamento del genitore, purché rimanga fedele al tipo di genitore.
C'è una buona lunga spiegazione su Wikipedia , che suggerisce le cose che violeranno LSP:
... ci sono una serie di condizioni comportamentali che il sottotipo deve soddisfare. Questi sono dettagliati in una terminologia simile a quella del design secondo la metodologia del contratto, portando ad alcune restrizioni su come i contratti possono interagire con l'eredità:
- Le condizioni preliminari non possono essere rafforzate in un sottotipo.
- Le postcondizioni non possono essere indebolite in un sottotipo.
- Gli invarianti del supertipo devono essere conservati in un sottotipo.
- Vincolo storico (la "regola della storia"). Gli oggetti sono considerati modificabili solo attraverso i loro metodi (incapsulamento). Poiché i sottotipi possono introdurre metodi che non sono presenti nel supertipo, l'introduzione di questi metodi può consentire cambiamenti di stato nel sottotipo che non sono ammessi nel supertipo. Il vincolo storico lo proibisce. Era il nuovo elemento introdotto da Liskov e Wing. Una violazione di questo vincolo può essere esemplificata definendo un MutablePoint come sottotipo di un ImmutablePoint. Questa è una violazione del vincolo storico, perché nella storia del punto Immutabile, lo stato è sempre lo stesso dopo la creazione, quindi non può includere la storia di un MutablePoint in generale. I campi aggiunti al sottotipo possono tuttavia essere modificati in modo sicuro perché non sono osservabili attraverso i metodi del supertipo.
Personalmente, trovo più semplice semplicemente ricordare questo: se sto osservando un parametro in un metodo con tipo A, qualcuno che passa un sottotipo B mi causerebbe sorprese? Se lo facessero, allora c'è una violazione di LSP.
Lanciare un'eccezione è una sorpresa? Non proprio. È qualcosa che può accadere in qualsiasi momento, sia che io stia chiamando il metodo Ship su OrderState o Granted o Shped. Quindi devo renderlo conto e non è davvero una violazione di LSP.
Detto questo , penso che ci siano modi migliori per gestire questa situazione. Se lo scrivessi in C #, utilizzerei le interfacce e verificherei l'implementazione di un'interfaccia prima di chiamare il metodo. Ad esempio, se l'attuale OrderState non implementa IShippable, non chiamare un metodo Ship su di esso.
Ma poi non userei nemmeno il modello statale per questa particolare situazione. Il modello di stato è molto più appropriato allo stato di un'applicazione che allo stato di un oggetto di dominio come questo.
Quindi, in poche parole, questo è un esempio mal concepito del modello di stato e non un modo particolarmente buono per gestire lo stato di un ordine. Ma probabilmente non viola LSP. E il modello statale, in sé e per sé, certamente no.