SOLID is a design principle used in software industry. The objective of this principle is to make software Scalable, Maintainable, Loosely coupled, Encapsulated etc. The major flaws in design to fail the software are as below:
- Assigning more and more responsibility to a single class.
- Depending one class on another class.
- Spreading/Introducing duplicate code in the system.
SOLID principle provide great help to overcome these design flaws.
SOLID Principle Details
- S: Single Responsibility Principle (SRP)
- O: Open Closed Principle (OCP)
- L: Liskov Substitution Principle (LSP)
- I: Interface Segregation Principle (ISP)
- D: Dependency Inversion Principle (DIP)
S: Single Responsibility Principle (SRP)
A Class or Module or Function should have only one job to do. In another
words, our Class or Module or Function should have only one reason to change.
It should not be like Swiss knife wherein if one of them need to change then
entire tool need to be altered. It does not mean that Class should only
contain one method or property. There may be many members as long as they
relate to single responsibility.
Example
User registration and Sending email are two distinct functionalities and there
is no relation between them so there should be two separate classes for both
the functionality.
In example 1 we can see that UserRegistration class is not following Single Responsibility Principle (SRP) because SendEmail and EmailValidation methods are totally different functionality so these method should not belong to UserRegistration class. Hence it is separated in example 2 to follow Single Responsibility Principle (SRP).
O: Open Closed Principle (OCP)
A Class or Module should be open for extension but closed for modification.
"Open for extension" means, we need to design our module/class in such a way
that the new functionality can be added only when new requirements are
generated. We can use inheritance for extension.
"Closed for modification"
means we have already developed a class and it has gone through unit testing.
We should then not alter it until we find bugs.
Example
In example 1, Calculator class is used to calculate the area of rectangle and
it is perfect.
But lets say tomorrow if we want to extend the Calculator class
by adding one more method to calculate area of circle then what will happen?
Definitely example 2 is one of the solution and it will work well. But we can
see that for every new method we have to modify the Calculator class with
multiple if-else statements. That means it is not following Open Closed
Principle (OCP).
Now see the example 3, we can add n-number of functionalities
without modifying the Calculator class. We only need to do is, declare one
class for each new functionality. Hence it is open for extension but closed
for modification and that's what the Open Closed Principle (OCP) is.
L: Liskov Substitution Principle (LSP)
The Liskov Substitution Principle (LSP) is just an extension of the Open
Closed Principle and ensure that a new class can be derived from a base class
without changing their behavior. In another words, a derived class must be
substitutable for its base class.
Example
In example 1, we want to define a class with read and write operations in
file. For this we have defined one File class and one FileManager class with
required functionalities and it work well.
Now tomorrow if we want to restrict write operation based on some condition. Means if file is read-only then write
operation should not be performed. To achieve this we have extended the File
class and create one ReadOnlyFile class as shown in example 2.
But what is wrong here??
Firstly, In ReadOnlyFile class we are throwing exception into the
SaveData method explicitly and secondly, we are modifying the FileManager
class by adding some explicit condition into SaveDataIntoFile method. That
means derived class is not a substitutable of it base class.
Now come to
example 3, to implement Liskov Substitution Principle (LSP), we have
restructured the program and spilt the File class into multiple classes based
on the functionalities using interfaces. Here we can see that any of the
derived class can easily substitute its base class.
I: Interface Segregation Principle (ISP)
Interface Segregation Principle (ISP) states that clients should not be forced
to implement methods of the interfaces which they don't use. Instead of one
fat interface should be splitted into many small interfaces with related
methods so that client can easily consume the interfaces without implementing
unnecessary methods.
Example
In example 1, we can see that ITask interface have three methods "CreateTask",
"AssignTask" and "Development". All three methods are relevant for TeamLead
class and has been implement by "TeamLead" class. But Development method of
the ITask interface is not relevant for "Manager" class because generally
manager don't do the development. But here we are forcing the "Manager" class
to implement "Development" method also unnecessarily. To mitigate this issue
Interface Segregation Principle (ISP) is need to be followed.
Now in example
2, we have splitted fatty interface ITask into two small interfaces called
ITask and IDevelopment with relavent methods. Now "Manager" class need to
implement only ITask interface and "TeamLead" class can implement both ITask
and IDevelopment interfaces. And thats what Interface Segregation Principle
(ISP) is.
D: Depedency Inversion Principle (DIP)
Dependency Inversion Principle (DIP) states that high-level modules/classes
should not depend on low-level modules/classes. Both high-level and low-level
module/classes should depend upon abstractions. And abstractions should not
depend upon details rather Details should depend upon abstractions.
High-level
modules/classes implement business rules or logic in a system (application).
Low-level modules/classes deal with more detailed operations; in other words
they may deal with writing information to databases or passing messages to the
operating system or services. So we must keep these high-level and low-level
modules/classes loosely coupled as much as we can. To do that, we need to make
both of them dependent on abstractions instead of knowing each other.
Example
In example 1, we can see that ExceptionLogger class is used to log the
exception into file using "FileLogger" class and it is working perfectly.
Now
tomorrow, we want to categorise our exception logging mechanism to log SQL
exception into database while keeping the existing file logging as it. To
achieve this, we have defined one more class as "DbLogger" and consuming that
class inside ExceptionLogger class. Which is also good but you can see that
for each new type of exception, we have to modify ExceptionLogger and that
will impact whole system along with existing clients.
Now to mitigate this
issue, we have to de-couple the structure in such a way that ExceptionLogger
is not needed to modify at all.
In example 3, we have de-coupled the design
using ILogger interface. Now you can see that both high-level and low-level
classes are depending on abstraction (i.e. ILogger) instead of knowing each
other. And thats what Depedency Inversion Principle (DIP) is.
Even in future,
if want to extend it further by adding one more type of exception (lets say
EventLogger) then also there is no need to modify the "ExceptionLogger" logger
class at all as shown in example 4. And we can keep extending without
impacting the existing system.
For details understanding and sample please follow my Github link.
Comments
Post a Comment