L - Liskov Substitution (LSP)
About
Objects of a Super-class should be able to be replaced with objects of a Sub-class, without any breaks.
Garantees that subclasses can be exchanged between themselves without any breaks.
Easy practical example
In practice have S as AxiosAdapter or FetchAdapter implements T HttpClient, you have to be able to change between Axios or Fetch without breaking execution/expectation.
The following code breaks LSP since you cannot change between adapters, because AxiosAdapter returns different data object than FetchAdapter. (So the Class Post Conditions are broken)
interface HttpClient {
get(url: string): Promise<any>;
post(url: string, data: any): Promise<any>;
}
// Returns `response.data`
class AxiosAdapter implements HttpClient {
get(url: string): Promise<any> {
return axios.get(url);
}
post(url: string, data: any): Promise<any> {
return axios.post(url, data);
}
}
// Returns `response.body`
class FetchAdapter implements HttpClient {
async get(url: string): Promise<any> {
const response = await fetch(url);
return response.json();
}
async post(url: string, data: any): Promise<any> {
const response = await fetch(url, {
method: 'post',
headers: { "content-type": "application/json" },
body: JSON.stringify(data)
});
return response.json();
}
}Defining subclasses only garantees syntax, but does not implies in keeping coherence and execution semantics.
From this principle we can derive some needed restrictions and requirements when making a Sub-class to avoid breaking LSP:
Parameters and Returns
Example Base
Contravariance of method parameters
It is type safe and preferred to allow an overriding method of a Sub-class to accept more general arguments than the method in the Super-class.
(Parameters passed cannot be strenghthened - more restrictive - in Sub-classes)
Covariance of method return types
An overriding method of a Sub-class may return more specific types than the method in the Super-class.
(Returned values cannot be weakened - less restrictive - in Sub-classes)
Exceptions thrown
New exceptions cannot be thrown by the methods in the Sub-class, except if they are Sub-classes of exceptions thrown by the methods of the Super-class.
New exceptions in Sub-classes may introduce unexpected behavior, that the Super-class did not have, violating the substitutability.
Pre & Post conditions
Preconditions
Cannot be strenghthened in the Sub-class.
Sub-classes should not impose stricter requirements than the Super-class. If they do, the Sub-class won't be usable in all contexts where the Super-class is expected.
Preconditions can be modified in redefined routines, but may only be weakened. (May lessen the obligation of the client, but not increase it)
An example of Preconditions being violated:
A resolution to this problem would be:
To use
Compositioninstead ofInheritance.Or define
Interfaceswith specific Contracts.
Postconditions
Cannot be weakened in the Sub-class.
Sub-classes must meet all the guarantees (outcomes or states) promised by the Super-class. If a Sub-class does less than what the Super-class guarantees, it will break client expectations.
Postconditions can be modified in redefined routines, but may only be strenghthened. (May increase the benefits it provides to the client, but not decrease those benefits)
An example of Postconditions being violated:
Invariance (Internal State)
Cannot be weakened in the Sub-class. (Should be preserved)
Last updated