SOLID Architectural Pattern With Real Time Example

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. 

  1. public class XmlValidator   
  2.     {  
  3.   
  4.         public void Validate()  
  5.         {  
  6.   
  7.               
  8.         }  
  9.          
  10.     }   

For updating a new class, it should be created. 

  1. public class XmlUpdate  
  2.     {  
  3.   
  4.         public void DoUpdate()  
  5.         {  
  6.   
  7.   
  8.         }  
  9.   
  10.   
  11.     }   

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 

  1. public class Customer  
  2.     {  
  3.         public int InvoiceNumber  
  4.         {  
  5.             get;  
  6.             set;  
  7.   
  8.         }  
  9.     }   

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. 

  1. public class CustomerNew : Customer  
  2.     {  
  3.   
  4.         public new String InvoiceNumber  
  5.         {  
  6.             get;  
  7.             set;  
  8.   
  9.         }  
  10.     }   

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. 

  1. public interface ISwitch  
  2.     {  
  3.         void On();  
  4.         void Off();  
  5.     }  
  6.   
  7.     public class Cooler : ISwitch  
  8.     {  
  9.         public void On()  
  10.         {  
  11.               
  12.         }  
  13.         public void Off()  
  14.         {  
  15.   
  16.         }  
  17.   
  18.         public void Regulate()  
  19.         {  
  20.   
  21.         }  
  22.   
  23.     }  
  24.   
  25.     public class Fan : ISwitch  
  26.     {  
  27.         public void On()  
  28.         {  
  29.   
  30.         }  
  31.   
  32.         public void Off()  
  33.         {  
  34.   
  35.         }  
  36.         public void Regulate()  
  37.         {  
  38.   
  39.         }  
  40.   
  41.     }  
  42.   
  43.     public class MainClass  
  44.     {  
  45.         public void AddObject()  
  46.         {  
  47.             List<ISwitch> Switch = new List<ISwitch>();  
  48.             Switch.Add(new Cooler());  
  49.             Switch.Add(new Fan());  
  50.             
  51.             foreach (var o in Switch)  
  52.             {  
  53.                 o.Regulate();  
  54.             }  
  55.         }  
  56.     }   

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. 

  1. public class Bulb : ISwitch  
  2.     {  
  3.         public void On()  
  4.         {  
  5.   
  6.         }  
  7.   
  8.         public void Off()  
  9.         {  
  10.   
  11.         }  
  12.         public void Regulate()  
  13.         {  
  14.             throw new NotImplementedException();  
  15.         }  
  16.   
  17.     }   

Now, AddObject method will be updated, as shown below. 

  1. public void AddObject()  
  2.         {  
  3.             List<ISwitch> Switch = new List<ISwitch>();  
  4.             Switch.Add(new Cooler());  
  5.             Switch.Add(new Fan());  
  6.             Switch.Add(new Bulb());            
  7.   
  8.             foreach (var o in Switch)  
  9.             {  
  10.                 o.Regulate();  
  11.             }  
  12.         }   

In this case, Regulate method will throw an error.

One horrible solution to this problem is to put an if condition. 

  1. foreach (var o in Switch)  
  2.             {  
  3.   
  4. if(o is Bulb)  
  5.     continue;  
  6.   
  7.                 o.Regulate();  
  8.             }   

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.

  1. public interface IClick{   
  2.     void onClick(Object obj);  
  3. }  

As time passes, new requirement comes for adding one more function onLongClick. You need to add this method in already created interface.

  1. public interface IClick{   
  2.     void onClick(Object obj);  
  3.     void onLongClick(Object obj);  
  4. }  

After some time, one new requirement comes for adding function for touch also and you need to add the method in the same interface

  1. public interface IClick{   
  2.     void onClick(Object obj);  
  3.     void onLongClick(Object obj);  
  4.     void onTouch(Object obj);  
  5. }  

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.

  1. public interface IClick{   
  2.     void onClick(Object obj);  
  3.     void onLongClick(Object obj);  
  4. }  
  5.   
  6. public interface ITouch{   
  7.     void onClick(Object obj);  
  8.     void onTouch(Object obj);  
  9. }  

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. 

  1. class Student  
  2. {  
  3.     // Handle to EventLog writer to write to the logs  
  4.     LogWriter writer = null;  
  5.     // This function will be called when the student has problem  
  6.     public void Notify(string message)  
  7.     {  
  8.         if (writer == null)  
  9.         {  
  10.             writer = new LogWriter();  
  11.         }  
  12.         writer.Write(message);  
  13.     }  
  14. }   

This is also a bad design. 

  1. class Student  
  2. {  
  3.     // Handle to EventLog writer to write to the logs  
  4.     LogWriter writer = null;  
  5. Public Student(LogWriter writer)  
  6.     {  
  7.   
  8.      This.writer = writer;  
  9. }  
  10.     // This function will be called when the student has problem  
  11.     public void Notify(string message)  
  12.     {  
  13.         writer.Write(message);  
  14.     }  
  15. }   

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. 

  1. class Student  
  2. {  
  3.     // Handle to EventLog writer to write to the logs  
  4.     ILogWriter writer = null;  
  5. Public Student(ILogWriter writer)  
  6.     {  
  7.   
  8.       This.writer = writer;  
  9. }  
  10.     // This function will be called when the student has problem  
  11.     public void Notify(string message)  
  12.     {  
  13.         writer.Write(message);  
  14.     }  
  15. }  

Next Recommended Readings