In an interview, an interviewer often asks for a real time example of a SOLID design pattern. Thus, I decided to write some real time examples of the SOLID design pattern.
What is SOLID?
SOLID is an acronym for five principles of architecture.
S – Single Responsibility Principle
O – Open Close Principle
L – Liskov Substitution Principle
I –Interface Segregation Principle
D – Dependency Inversion Principle
Single Responsibility Principle (SRP)
It says that every class should have single responsibility. A class should not have more than one reason to change.
Example
Suppose, you have created a class XmlValidator for XML validation, which has the responsibility to validate XML.
If there is a need to update XML, then a separate class should be created for the same. XmlValidator class should not be used for updating XML.
- public class XmlValidator
- {
-
- public void Validate()
- {
-
-
- }
-
- }
For updating a new class, it should be created.
- public class XmlUpdate
- {
-
- public void DoUpdate()
- {
-
-
- }
-
-
- }
Open Close Principle (OCP)
A class should be open for an extension and closed for the modification.
Example
Suppose, we have a class name Customer, which has a property InvoiceNumber, which has an integer type
- public class Customer
- {
- public int InvoiceNumber
- {
- get;
- set;
-
- }
- }
In the future, if the requirement changes now, InvoiceNumber should be alphanumeric rather than only an integer. Hence, in this case, you should create a subclass CustomerNew with a same property but different datatype rather than modifying the previous one.
- public class CustomerNew : Customer
- {
-
- public new String InvoiceNumber
- {
- get;
- set;
-
- }
- }
Liskov Substitution Principle (LSP)
A parent object should be able to replace its child during runtime polymorphism.
Example
Suppose, you have two classes, Cooler and Fan, both are inherited from a common interface named ISwitch, which has three methods- On, Off and Regulate.
- public interface ISwitch
- {
- void On();
- void Off();
- }
-
- public class Cooler : ISwitch
- {
- public void On()
- {
-
- }
- public void Off()
- {
-
- }
-
- public void Regulate()
- {
-
- }
-
- }
-
- public class Fan : ISwitch
- {
- public void On()
- {
-
- }
-
- public void Off()
- {
-
- }
- public void Regulate()
- {
-
- }
-
- }
-
- public class MainClass
- {
- public void AddObject()
- {
- List<ISwitch> Switch = new List<ISwitch>();
- Switch.Add(new Cooler());
- Switch.Add(new Fan());
-
- foreach (var o in Switch)
- {
- o.Regulate();
- }
- }
- }
Everything was fine until a new class introduced for same interface named Bulb, which has only two methods On and Off. It does not have Regulate method. Thus Bulb class is given below.
- public class Bulb : ISwitch
- {
- public void On()
- {
-
- }
-
- public void Off()
- {
-
- }
- public void Regulate()
- {
- throw new NotImplementedException();
- }
-
- }
Now, AddObject method will be updated, as shown below.
- public void AddObject()
- {
- List<ISwitch> Switch = new List<ISwitch>();
- Switch.Add(new Cooler());
- Switch.Add(new Fan());
- Switch.Add(new Bulb());
-
- foreach (var o in Switch)
- {
- o.Regulate();
- }
- }
In this case, Regulate method will throw an error.
One horrible solution to this problem is to put an if condition.
- foreach (var o in Switch)
- {
-
- if(o is Bulb)
- continue;
-
- o.Regulate();
- }
This is an example of bad design, if above condition is used somewhere, it clearly means that there is a violation of LSK principle.
Interface Segregation Principle (ISP)
Client specific interfaces are better than general purpose interfaces.
Suppose, we have one interface for clicking.
- public interface IClick{
- void onClick(Object obj);
- }
As time passes, new requirement comes for adding one more function onLongClick. You need to add this method in already created interface.
- public interface IClick{
- void onClick(Object obj);
- void onLongClick(Object obj);
- }
After some time, one new requirement comes for adding function for touch also and you need to add the method in the same interface
- public interface IClick{
- void onClick(Object obj);
- void onLongClick(Object obj);
- void onTouch(Object obj);
- }
At this point, you need to decide to change the name of interface too because touch is different than click.
In this way, this interface becomes a problem—generic and polluted. At this stage, ISP comes into play.
Why Generic Interface creates problem?
Suppose, some clients need only onClick function and some need only onTouch function, then one will be useless for both. Hence ISP gives the solution, which splits the interface into two interfaces.
ITouch and IClick. The client which has required onClick can implement IClick, which needs onTouch. It can implement ITouch and when it needs both, it can implement both.
- public interface IClick{
- void onClick(Object obj);
- void onLongClick(Object obj);
- }
-
- public interface ITouch{
- void onClick(Object obj);
- void onTouch(Object obj);
- }
Dependency Inversion Principle (ISP)
It states two points, where the first point is a higher level module, which should not depend on a low level module. Both should depend on abstraction. The second point is abstraction, which should not depend on detail.
Detail should depend on abstraction.
In other words, no object should be created inside a class. They should be passed or injected from outside. When it is received, it will be an interface rather than a class.
Here is a bad design without using Dependency Injection.
- class Student
- {
-
- LogWriter writer = null;
-
- public void Notify(string message)
- {
- if (writer == null)
- {
- writer = new LogWriter();
- }
- writer.Write(message);
- }
- }
This is also a bad design.
- class Student
- {
-
- LogWriter writer = null;
- Public Student(LogWriter writer)
- {
-
- This.writer = writer;
- }
-
- public void Notify(string message)
- {
- writer.Write(message);
- }
- }
The written classes given above are bad designs because in the case of a change in LogWrite, you have to disturb Student class. We should use an interface inside Student instead of a class.
With Dependency Injection, the code is given, as shown below.
- class Student
- {
-
- ILogWriter writer = null;
- Public Student(ILogWriter writer)
- {
-
- This.writer = writer;
- }
-
- public void Notify(string message)
- {
- writer.Write(message);
- }
- }