May be its easy to forget these principles but for creating a reusable code or
maintainable code, people will always have to come back and check these principles
for robust design. Today, We will explore these principles, not in terms of definition
or meaning but why they are required or how they can help in designing our system.
S –Single responsibility principle
Wikipedia defines it as “every class should have a single responsibility,
and that responsibility should be entirely encapsulated by the class”.
The idea behind this principle is that, if you have a class and there are some
situation like
-
there are dependent or inherited classes.
-
there is some consumer flow attached to it.
So if we go ahead and introduce any change to our main class then this change
should only be limited to this class . None of the dependent classes or consumers
should have to be modified to adjust to this change.
How and why?
By following this principle, we can limit the number of places we have to change
the code for a single change anywhere. It can facilitate clean code and division.
Other than the advantages mentioned above, following this principle leads to
-
Simple code.
-
understandable design.
-
Maintainability
O –Open/closed principle
Wikipedia definition says “software entities (classes, modules, functions,
etc.) should be open for extension, but closed for modification”
When we write a code, we ,somewhat has to predict the possible changes coming
, as it is a well-known fact that the code that we are writing is susceptible to
change when we are writing it. However if the developer is inexperienced and is
not able to predict the possible changes, so to deal with it ,go ahead and write
the simple code.
How and why?
Although, the moment the changes are requested then he should be ready to introduce
this principle and works on the extension of the code and make it more robust.
As per the principle, software design should be open for extensions and closed
for modifications . This is a tough prediction and involvement of developer / designer
is required to identify what areas of his code are susceptible to change.
L – Liskov Substitution Principle
Wikipedia definition – “if S is a subtype of T, then objects of type T may
be replaced with objects of type S”
This line always confused the readers as they talk about interchangeable principle
between base types and sub-types. if sub-types can replace base-types then why do
we required base types at all?
The primary objective of this design is that the code while calling should not
know the difference between the base type and a sub type. If your code is using
base-type instance as a parameter then if we replace the parameter with sub-type,
then also, our code should work.
One area where this principle can NOT be applied is when the subtype implement
only one (or few) operations of the base type. This means that subtypes are not
exactly replaceable substitutes of base-type.
How and why?
Imagine you have a code to book a vehicle for transportation, this ‘vehicle’
instance is a parameter in this code, trust this ‘vehicle’ instance as a base-type
and if you replace it with a sub-type like ‘Bus’ then also your code for transportation
should work.
Any operation that can be performed on a subtype but cannot be done on another
subtype with the same parent, then this principle is compromised.
I – Interface Segregation Principle
For this principle, idea is to use customer centric interface. How? class and
interface are interchangeable terms.
When client asks for only a few operations, then we should provide only those
operation with the help of interface . Don’t try to build a single interface, but
try to declare many individual interfaces.
Interface segregation violations result in classes that depend on interfaces
they do not need, increasing coupling and reducing flexibility and maintainability.
How and Why?
If you have built a fat interface, and Client references this interface but only
uses a part of it then your principle is not valid. This is where coder has to provide
multiple interfaces so that client will refer to only those interfaces which are
required and leave other interfaces.
If you have a problem using a fat interface, then go for adapter pattern and
generate a single interface which will work as an adapter to this fat interface.
D – Dependency Injection / Inversion of control
We have .Net MVC fully utilizing this principle.
Wikipedia: The principle states:
-
- A. High-level modules should not depend
on low-level modules. Both should depend on
abstractions.
- B. Abstractions should not depend upon details.
Details should depend upon abstractions.
Here, the definition is much more clear but still difficult to implement. Idea
is that, we should not declare tight coupling between details and abstractions.
Like, If i have a phone and i just want to send somebody a message through this,
then I’ll just be dependent on my phone interface and not worried about the network
as how it will reach the reception point.
How and why?
First of all, chuck out all the dependencies that are in your implementation.
Once you are able to chuck out, start changing the code and provide an option to
introduce these dependencies from outside.
you can use patterns like Unity and all to learn the principle.
CodeProject