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.

circle-info

If Class S extends Class T, then Object<T> could be replaced by Object<S> without any breaks.

circle-check

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();
    }
}
circle-exclamation

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)

circle-info

It might seem intuitive that a subclass should be “more restrictive” since it’s specialized, but contravariance exists to ensure that the subclass can accept anything the superclass can and still operate correctly.

This makes substitutability possible while still allowing the Sub-class to handle inputs in more specific ways if needed (like the credit card limit check in the example).

LSP requires contravariant parameters because:

  • It ensures that a subclass can be used wherever the Super-class is expected without introducing type conflicts.

  • It keeps the behavior general and interchangeable at the point of use, which is fundamental to achieving polymorphism.

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

circle-info

Refer to the conditions that must be true before a method is called.

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:

circle-exclamation

Postconditions

circle-info

Refer to the conditions that should be true after a method completes sucessfully.

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