a sublcass shouldn't alter the behavior of the parent?
Questa è una comune interpretazione errata di LSP. Una sottoclasse può alterare il comportamento del genitore, a patto che rimanga fedele al tipo genitore.
C'è una buona spiegazione lunga su Wikipedia , che suggerisce le cose che violeranno LSP:
... there are a number of behavioral conditions that the subtype must meet. These are detailed in a terminology resembling that of design by contract methodology, leading to some restrictions on how contracts can interact with inheritance:
- Preconditions cannot be strengthened in a subtype.
- Postconditions cannot be weakened in a subtype.
- Invariants of the supertype must be preserved in a subtype.
- History constraint (the "history rule"). Objects are regarded as being modifiable only through their methods (encapsulation). Since subtypes may introduce methods that are not present in the supertype, the introduction of these methods may allow state changes in the subtype that are not permissible in the supertype. The history constraint prohibits this. It was the novel element introduced by Liskov and Wing. A violation of this constraint can be exemplified by defining a MutablePoint as a subtype of an ImmutablePoint. This is a violation of the history constraint, because in the history of the Immutable point, the state is always the same after creation, so it cannot include the history of a MutablePoint in general. Fields added to the subtype may however be safely modified because they are not observable through the supertype methods. One may derive a CircleWithFixedCenterButMutableRadius from ImmutablePoint without violating LSP.
Personalmente, trovo più semplice ricordare semplicemente questo: se guardo un parametro in un metodo che ha tipo A, qualcuno che passa un sottotipo B mi causerebbe qualche sorpresa? Se poi ci fosse una violazione di LSP.
Lanciare un'eccezione è una sorpresa? Non proprio. È qualcosa che può accadere in qualsiasi momento, sia che chiami il metodo Ship su OrderState sia Grant o Shipped. Quindi devo renderlo conto e non è in realtà una violazione di LSP.
Detto questo , penso che ci siano modi migliori per gestire questa situazione. Se scrivessi questo in C #, userei le interfacce e controllerei l'implementazione di un'interfaccia prima di chiamare il metodo. Ad esempio, se l'attuale OrderState non implementa IShippable, non chiamare su di esso un metodo Ship.
Ma anche io non utilizzerei il pattern State per questa particolare situazione. Lo schema di stato è molto più appropriato allo stato di un'applicazione rispetto allo stato di un oggetto di dominio come questo.
Quindi, in breve, questo è un esempio poco elaborato del modello di stato e non un modo particolarmente valido per gestire lo stato di un ordine. Ma probabilmente non viola LSP. E lo schema di stato, in sé e per sé, certamente non lo è.