# 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.

{% hint style="info" %}
If `Class S extends Class T`, then `Object<T>` could be replaced by `Object<S>` without any breaks.
{% endhint %}

{% hint style="success" %}
**Garantees that subclasses can be exchanged between themselves without any breaks.**
{% endhint %}

### Easy practical example

&#x20;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***](#pre-and-post-conditions) *are broken)*

```typescript
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();
    }
}
```

{% hint style="warning" %}
Defining subclasses only garantees syntax, but does not implies in keeping coherence and execution semantics.
{% endhint %}

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

```typescript
class AnimalShelter {
    putAnimal(Animal animal): void { ... }
    takeAnimal(): Animal { ... }
}

class CatShelter extends AnimalShelter {
    ...
}
```

### 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)*

{% hint style="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.
  {% endhint %}

```typescript
// If overriding `putAnimal`
class CatShelter extends AnimalShelter {
    /*
        I could pass a LivingBeing, but I should NOT pass a Cat.
    */
    putAnimal(LivingBeing lb): void { ... }
    putAnimal(Cat cat): void { ... }  // WRONG
}
```

### 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)*

```typescript
// If overriding `takeAnimal`
class CatShelter extends AnimalShelter {
    /*
        I can return a Cat, but I should NOT return an LivingBeing.
        (Considering LivingBeing something more general than Animal)
    */
    takeAnimal(): Cat { ... }
    takeAnimal(): LivingBeing { ... }  // WRONG
}
```

#### 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

{% code title="Ex.:" %}

```typescript
class PaymentProcessor {
    // Preconditions: amount should be a positive number.
    // Postconditions: returns a success message with the processed amount.
    process(amount: number): string {
        if (amount <= 0) throw new Error("Amount must be positive.");
        return `Processed payment of $${amount}`;
    }
}
```

{% endcode %}

### Preconditions

{% hint style="info" %}
Refer to the conditions that must be true before a method is called.
{% endhint %}

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:*

{% code title="Ex.:" %}

```typescript
class CreditCardProcessor extends PaymentProcessor {
    private limit: number = 5000;
    
    process(amount: number): string {
        if (amount <= 0) throw new Error("Amount must be positive.");
        /*
            Here we are strengthening the method's preconditions.
            
            By throwing an Error, thus any code that expects `process` to handle
            numbers over 5000 will encounter unexpected behavior.
        */
        if (amount > this.limit) throw new Error("Amount exceeds limits");
        return `Processed payment of $${amount}`;
    }
}
```

{% endcode %}

{% hint style="warning" %}
A resolution to this problem would be:

* To use `Composition` instead of `Inheritance`.
* Or define `Interfaces` with specific Contracts.
  {% endhint %}

### Postconditions

{% hint style="info" %}
Refer to the conditions that should be true after a method completes sucessfully.
{% endhint %}

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:*

{% code title="Ex.:" %}

```typescript
class CreditCardProcessor extends PaymentProcessor {
    private limit: number = 5000;
    
    process(amount: number): string {
        if (amount <= 0) throw new Error("Amount must be positive.");
        /*
            Here we are weakening the method's postconditions.
            
            By returning a new type of message (Error:) that is unexpected, remember that the
            postcondition from the Super-class was to return a success message only.
        */
        if (amount > this.limit) return `Error: Amount exceeds limits.`;
        return `Processed payment of $${amount}`;
    }
}
```

{% endcode %}

## Invariance (Internal State)

Cannot be weakened in the Sub-class. *(Should be preserved)*
