La sostituibilità di Liskov non significa che puoi sostituire un oggetto di una sottoclasse per un oggetto di una superclasse e il tuo programma sarà ancora corretto . L'unica cosa che garantisce è che le promesse fatte dalla superclasse saranno onorate da tutte le sottoclassi .
Nel tuo caso, la superclasse ha promesso "La mia velocità non supererà i 100" e mantiene questa promessa. Anche la sottoclasse mantiene questa promessa.
La sottoclasse introduce anche una promessa nuova e più severa: " La mia velocità non supererà i 50" e onora anche questa promessa. Questo è possibile perché gli stati ammessi dalle nuove premesse sono un sottoinsieme ristretto degli stati consentiti dalla vecchia promessa.
La superclasse non onora questa nuova promessa, anzi non ne è nemmeno a conoscenza. Ciò significa che il suo comportamento sarà diverso dal comportamento della sottoclasse. Ovviamente, questo può influire sulla correttezza del programma - la sostituibilità non significa che qualsiasi classe in una gerarchia di classi è buona come l'altra.
Allora a che serve la sostituibilità di Liskov? È buono solo come il sistema di tipi nella tua lingua. Fa un buon lavoro rafforzando le pre e postcondizioni che il sistema di tipi nella tua lingua può esprimere. Ad esempio, una volta che dichiari un metodo che restituisce un unsigned int
, puoi essere certo che non restituirà mai un valore negativo, e nessuno dei due avrà una sottoclasse . Ma probabilmente non è possibile esprimere "questo metodo trova la migliore classificazione dei valori di input vicini più vicini" nel sistema di tipi, pertanto l'LSP non può garantire la correttezza del programma in base all'ereditarietà. (Per essere onesti, quasi nessun formalismo può.)