SOLID

The applicability of SOLID principles, has the objective of valuing:

  • Object Oriented Design.

    • High cohesion.

    • Low coupling.

      • The intimacy, Object A knows B, B knows C and A, etc...

      • The problem is not only knowing, but to know the inner characteristics.

  • Fragility reduction.

  • Increase reuse.

  • Reduce rigidity.

S (Single Responsibility Principle - SRP)

A class should have one responsability.

A class must have one reason to change, if there are many reasons, they must be separated to other classes.

O (Open Closed Principle - OCP)

Classes should be open to extension and closed for modification.

The behavior of a module can be extended without modifying its source code.

Whenever you need to add new functionality to a class, don't just modify the existent class.

Instead create a base class, and for each added functionality you make it in a new class to extend the base one.

Using Factories to create similar implementations.

Drawing

L (Liskov Substitution Principle - LSP)

Objects of a Super-class should be able to be replaced with objects of a Sub-class, without any breaks.

If Class S extends Class T, then Object<T> could be replaced by Object<S> 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)

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)

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

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:

Postconditions

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)

I (Interface Segregation Principle - ISP)

A class should not implement interfaces that it won't use.

Create interfaces based only on what the client needs, avoiding dependencies on things it won't use.

Drawing

Example

In this example, the Exec class, which is the class that uses the method, specifies what it wants from ClassA.

So now, Exec don't know ClassA anymore, it only knows Interface2. And if ClassA changes in the future, Exec won't need to be re-compiled (Since there is no more import A from '.../ClassA').

D (Dependency Inversion Principle - DIP)

High level modules must not depend in low level ones. Both must depend on abstractions and not implementations.

  • Separated (independent) modules, must not have direct dependencies between them.

  • A class must not know the implementation of other classes methods, but know only the interface of that class.

Drawing

Last updated