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.
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.
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)
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)
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:
A resolution to this problem would be:
To use
Composition
instead ofInheritance
.Or define
Interfaces
with specific Contracts.
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.
Usually impacts reduction of unecessary re-compilation.
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)
The closest to business rules, the higher the level.
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.
Last updated