Consider that you are working in good startup based company. There you were assigned with the role of Software Development Engineer. So that, you are the most responsible person for developing core features from end to end.

But the problem is, in early stage startups, things are not very organized. So we ended up by adding new features daily, no formal process, very dynamic environment, no time to worry about code structure and all.

For example, assume that you are working in a project of E Commerce web development, ultimately you work on to add, store, delete, process and retrieve projects from databases through API’s. Consider that you have nearly 1000 products and its details from database, after two years one client is asking to add one more product in that database.

Working on a startup product

  1. Constantly adding new features
  2. No formal process
  3. No time to worry about code structure
  4. What is it like to go back to your code after 2 years?

Because of these two year gaps, when you come and read your old code, you seems to be not even understand your code😫.

Any how by severe walkthrough, debugging one method to another method and from declaration to another and from file to another, finally you came to understand that, what you did two years ago.

And then, to add one product in the database, you are modifying several codes, once again restructuring the class, you are taking nearly two to three days to modify that😣OMG.

Do you think that, how frustrating this is ? Going into your same code and feeling difficult to change a simple things from that.

How to solve this problem by using SOLID design principles?

Main Purpose of SOLID design principles

  1. To make the code more maintainable
  2. To make it easier to quickly extend the system with new functionality without breaking the existing ones.
  3. To make the code easier to read and understand, thus spend less time figuring out what it does and more time developing the actually developing the solution.

Motivation behind the usage of SOLID Principles

1) Maintainability

In any enterprise software application development which many of us are part of it, we design and develop software systems and in that process we need to account the below factors during the development lifecycle.

The first thing is Maintainability. When any enterprise systems grows big and the expected lifetime growing longer and longer, maintaining those systems becomes more and more complex and it becomes a day to day challenge. Many of us has been experienced at that as well.

The original team that develops the system becomes no longer available due to their career growth or any other factors. Sometimes you know the documentation becomes out of sync. All these factors influence the maintainability of a system along with a demanding need of upgrading these systems.

Hence maintainable systems are very important to the organizations.

2) Testability

Test driven development is required when we design and develop large scale systems. It’s good practice to have a test driven development in the system development process itself.

Hence test driven development is very important in the application lifestyle.

3) Flexibility and Extensibility

Flexibility and extensibility is very much desired factor of enterprise applications. As a developer, we faced many situation of changing the business requirements very often during both the development of an application as well as after being it’s deployed to production.

Hence we should design the application with the flexible and extensible way.

4) Parallel Development

It is one of the key feature in the application development. As it is not practical to have the entire development team working simultaneously on the same feature or component.

5) Loose Coupling

And the last one is loose coupling, we can address many of the requirements listed above by ensuring that our design results in an application that loosely couples many parts that makes up the application.

Loose coupling makes the application easier and safer to make any changes in one area of the system because each system is largely independent of other.

Hence SOLID principles and design patterns plays a key role in achieving all of the above points.

SOLID Introduction

SOLID principles are the design principles that enable us to manage most of the software design problems.

These principles are a subset of many principles introduced by Robert C. Martin (Uncle Bob). After some time, the SOLID acronym was introduced by Michael Feathers.

SOLID Acronym

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)

1) Single Responsibility Principle (SRP)

Single Responsibility Principle ensures that, A class should have one, and only one, reason to change.

A class should only be responsible for one thing.

In other words, Every class or module should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by a class.

  • Each class and module should focus on a single task at a time.
  • Everything in the class should be related to that single purpose.
  • There can be many members in the class as long as they related to the single responsibility.
  • With Single responsibility principle, classes become cleaner and smaller.

As we see that, Single responsibility principle achieves the motivations of SOLID design principles.

Example:

Consider that, we want to build an application that performs login and registration. Not only login and registration, it should send the verification mail to user who registered in. Also it should log the errors when errors occurs during login and registration attempt time.

How can we program that?

So the normal tendency is to create an interface with all these methods. Let’s first create an interface with the name as IUser.

Let’s open our Visual Studio 2019, Right click on the project and choose Add->Interface.

Let’s create some methods in the interface.

The first method is Login method with username and password as parameters. The second method will be Register method, it has username, password and email as input parameters. Now in this process we need to log any exceptions. Let’s create the another method which logs the exception. It receives the error as a input parameter. And the next method is SendVerificationMail, when the user successfully registered in.

These are the four methods which are required in minimum for any user to perform login or registrations.

At a first glance, it looks very common to have these steps performed during the user registration process.

But if you take a deep look into this methods, we can definitely figure out that, we don’t need to have LogError() and SendVerificationMail() part of IUser() interface.

Because User object is need to perform only for User Login and User Registration. But it should not be concerned about LogError() and SendVerificationMail().

We no need LogError() and SendVerificationMail() as part of IUser interface. We definitely need to break that down into separate interfaces.

Let’s do that now,

Create a another interface with the name as ILogger interface. The method that, this interface would handle is LogError() method.

Also let’s create a another interface with the name as interface. The method that, this interface would handle is SendVerificationMail() method.

Now having these interfaces makes more sense. And these three interfaces are performing their own responsibilities. Such as IUser performs user related responsibilities, ILogger performs the logging responsibilities and IEmail performs SendingEmail and email related responsibilities.

Note that, we have segregated the single responsibility principle using these multiple interfaces.

2) Open Closed Principle (OCP)

Open Closed Principle ensures that, Software entities such as Classes, Modules, Functions etc. Should be open for extension, but closed for modification.

In other words, any new functionality should be implemented by adding new classes, attributes and methods, instead of changing the current ones or existing ones.

Open for Extension

Closed for Modification

Motto : NEVER REWRITE CODE

It says that, you should never have to rewrite any of your code, you should write only new code.

You should never rewrite your existing code.

You should only add new code to your codebase.

If you able to do like that, then it would be easier to avoid bugs in your code, avoid problems that evolves over time.

How to apply Open Closed Principle?

The simplest way to apply Open Closed Principle is to implement the new functionality on new derived classes.

Importance of Open Closed Principle

If we not follow the Open Closed Principle, finally we end with following disadvantages.

  1. If a class or function always allows the addition of new logic as a developer we end up testing the entire functionality along with the requirement.
  2. Also QA team need to test the entire flow
  3. It breaks the Single responsibility principle.
  4. Also if the changes are implemented on the same class, maintenance of class becomes difficult, since the code of the class increases by thousands of unorganized lines.

Example:

Consider that we an Employee class, we need to compute bonus of Employee.

Here is our driver class,

This code works great, when we run this program, we get output as:

Let’s say there is some enhancement or new requirement, to differentiate bonus and contract Employee. In a typical scenario, we tend to modify the class by adding an Employee type property and enhance the current method to account for the Employee type while calculating the bonus.

Let’s add an Employee type to that class and also lets add Employee type in constructor.

While calculating the bonus, we need to verify the Employee type. If the given employeeType is “Permanent”,then the bonus would be 10%, otherwise the bonus would be 5%.

Let’s modify the driver program also,

Here is the output for contractor employee bonus,

As a developer we are happy, our code is working as expected. But remember we changed the method as per our new requirement. If we need to add more requirements in future, we end up enhancing the same method to support new requirements.

Hence we say that, this class is not closed for modification. Since it is not following the OCP principle, it would have several drawbacks we discussed above.

Time to address this issue:

  1. The first thing we need to make the employee class as an abstract class and leave the implementation of calculating bonus to the derived classes. This helps in extending the class for further enhancements without touching the base class.

Note that, based up on our need, we have option to choose interface or abstract classes. Here I’m going with abstract classes.

As per our requirement we need to create an PermanentEmployee and its CalculateBonus() method.

Also let’s create the constructors for PermanentEmployee class.

Here is our ContractEmployee class,

Here is our new driver class😉,

Let’s see the output of above program,

Notice that, bonus of permanent and contract employee is unchanged. We got the result as protected.

Also we can that, Employee class is open for extension and closed for modification.

I hope now, you got a better understanding of implementing the Open Closed Principle(OCP).

3) Liskov Substitution Principle (LSP)

According to wikipedia, Substitutability is a principle in object-oriented programming stating that, in a computer program, if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e. an object of type T may be substituted with any object of a subtype S) without altering any of the desirable properties of the program (correctness, task performed, etc.). More formally, the Liskov substitution principle (LSP) is a particular definition of a subtyping relation, called (strongbehavioral subtyping, that was initially introduced by Barbara Liskov in a 1987 conference keynote address titled Data abstraction and hierarchy.

🥱😴😫 Oh my GOD, what is this?

When comes to object oriented programming, Liskov substitution principle is slightly confusing.

Let’s break down the definition into small manageable pieces. Let’s see the simple explanation of the concept.

Liskov substitution principle ensures that, “Ability to replace any instance of a parent class with an instance of one of its child classes without negative side effects“.

S is a subtype of T, then objects of type T may be replaced with objects of type S. It means that, Derived types must be completely substitutable for their base types.

It was introduced by Barbara Liskov.

Liskov Substitution Principle is the extension of the Open Closed Principle.

Implementation Guidelines

  1. In the process of development, we should ensure that, no new exceptions can be thrown by the subtype unless there is part of an existing exception hierarchy.
  2. We should also ensures that, Clients should not know which specific subtype they are calling.
  3. New derived classes just extend without replacing the functionality of old classes.

Right now, it seems to be little confusing, Don’t worry! I make it clear, by explaining with simple example😀😀😀

Consider the above example which we discussed in Open Closed Principle(OCP), we have created different employee classes to calculate bonus of the employee. We have inherited the Employee class in Permanent and Contractor employee classes adhering to the Open Closed Principle(OCP).

using System;
using System.Collections.Generic;
using System.Text;

namespace CSharpDesignPatterns
{
    public abstract class Employee
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public Employee(int id, string name)
        {
            this.ID = id;
            this.Name = name;
        }
        public abstract decimal CalculateBonus(decimal salary);
        public override string ToString()
        {
            return string.Format("ID : {0} Name : {1} ", this.ID, this.Name);
        }
    }
    public class PermanentEmployee : Employee
    {
        public PermanentEmployee(int id,string name) : base(id,  name)
        {

        }
        public override decimal CalculateBonus(decimal salary) 
        {
            return salary * .1M;
        }
    }
    public class ContractEmployee : Employee
    {
        public ContractEmployee(int id, string name) : base(id,name)
        {

        }
        public override decimal CalculateBonus(decimal salary)
        {
            return salary * .05M;
        }
    }
}

From the employee perspective, we have implemented successfully the Open Closed Principle(OCP).

Let’s see the driver program now,

using System;

namespace CSharpDesignPatterns
{
    class Program
    {
        static void Main(string[] args)
        {
            Employee empObj1 = new PermanentEmployee(1,"Mei");
            decimal bonus1 = empObj1.CalculateBonus(100000);

            Employee empObj2 = new ContractEmployee(2, "Nitin");
            decimal bonus2 = empObj2.CalculateBonus(100000);

            Console.WriteLine("Bonus of Permanent employee is {0}", bonus1);

            Console.WriteLine("Bonus of Contract employee is {0}", bonus2);

        }
    }
}


When you look at the main program, we have created the Employee objects consisting of both permanent and temporary employees.

When you look at deeply, the derived types PermanentEmployee and ContractEmployee have completely substituted the base type Employee class.

Based on the Liskov Substitution principle, we have achieved that, by ensuring that derived classes are completely substitutable for their base classes.

Also if you notice in the main program,

Without using the subtypes we are calculating the bonus of the employee, from the base class itself. Hence we are partially satisfying the listkov principle here.

That is, along with the Open Closed Principle(OCP), we have partially implemented the liskov principle here.

I said partially, because this program still violates one of the guidelines of liskov principle. What is that? Yea this one, “In the process of development, we should ensure that, no new exceptions can be thrown by the subtype unless there is part of an existing exception hierarchy.”

For example, I want to add one more TemporaryEmployee class, temporary employee don’t have the bonus.

Let’s implement in visual studio,

It’s our TemporaryEmployee class, it is inheriting the abstract Employee class, since it is deriving from Employee class, it is asking to implement the abstract CalculateBonus() method. But for TemporaryEmployee class, we don’t have bonus.

Look deeply that, this derived class is throwing the NotImplementedException()😮

It is violating the liskov design principle of “In the process of development, we should ensure that, no new exceptions can be thrown by the subtype unless there is part of an existing exception hierarchy”.

Let’s modify our driver class, to call TemporaryEmployee class,

When i run this program, it compiled fine and thrown a run time error in Employee class😯😯😯😫

How can we solve that? How can we achieve the Liskov Substitution Principle here?

Let’s rewrite this code in different way to achieve the Liskov principle,

Create the IEmployeeBonus interface, consisting of interface method as CalculateBonus()

using System;
using System.Collections.Generic;
using System.Text;

namespace CSharpDesignPatterns
{
    interface IEmployeeBonus
    {
        decimal CalculateBonus(decimal salary);
    }
}

Initially, CalculateBonus() method was under abstract Employee class, now I moved into IEmployeeBonus interface.

Alone with this interface, I have created another interface called IEmployee.

using System;
using System.Collections.Generic;
using System.Text;

namespace CSharpDesignPatterns
{
    interface IEmployee
    {
        int ID { get; set; }
        string Name { get; set; }
    }
}

Coming back to the Employee class, I have inherited the IEmployee and IEmployeeBonus interface in Employee class,

public abstract class Employee : IEmployee, IEmployeeBonus
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public Employee(int id, string name)
        {
            this.ID = id;
            this.Name = name;
        }
        public abstract decimal CalculateBonus(decimal salary);
        public override string ToString()
        {
            return string.Format("ID : {0} Name : {1} ", this.ID,this.Name);
        }
    }

Let’s see the PermanentEmployee class,

 public class PermanentEmployee : Employee
    {
        public PermanentEmployee(int id,string name) : base(id,  name)
        {

        }
        public override decimal CalculateBonus(decimal salary) 
        {
            return salary * .1M;
        }
    }

Notice that, we retained the Employee class in PermanentEmployee class.

Now, let’s see the ContractorEmployee class,

 public class ContractEmployee : Employee
    {
        public ContractEmployee(int id, string name) : base(id,name)
        {

        }
        public override decimal CalculateBonus(decimal salary)
        {
            return salary * .05M;
        }
    }

Even in the ContractorEmployee class, we inheriting the same Employee class.

So the major change in the TemporaryEmployee class,

public class TemporaryEmployee : IEmployee
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public TemporaryEmployee(int id, string name) 
        {
            this.ID = id;
            this.Name = name;
        } 
    }

Since TemporaryEmployee don’t have the bonus, so that we are not inheriting the IEmployeeBonus interface😇

Let’s see the main program now,

We are creating the object for TemporaryEmployee class, since it don’t have the bonus, it is throwing the compilation error. Thus we avoid the run time exceptions and satisfying the Liskov substitution principle.

Let’s remove the bonus3 from our program, then our program finely satisfying the liskov substitution principle.

See the below final code which is satisfying the liskov substitution design principle,

using System;

namespace CSharpDesignPatterns
{
    class Program
    {
        static void Main(string[] args)
        {
            Employee empObj1 = new PermanentEmployee(1,"Mei");
            Employee empObj2 = new ContractEmployee(2, "Nitin");

            IEmployee tempEmpObj = new TemporaryEmployee(3, "Mathan");


            decimal bonus1 = empObj1.CalculateBonus(100000);
            decimal bonus2 = empObj2.CalculateBonus(100000);
            //decimal bonus3 = tempEmpObj.CalculateBonus(100000);

            Console.WriteLine("Bonus of Permanent employee is {0}", bonus1);

            Console.WriteLine("Bonus of Contract employee is {0}", bonus2);

            Console.Write("Name and Id of Temporary Employee is {0} and {1}", tempEmpObj.Name, tempEmpObj.ID);

        }
    }
}


Output of the above program:

Without any run time error, our liskov substitution design principle example program is running efficiently.

4) Interface Segregation Principle (ISP)

Interface Segregation Principle ensures that, “No Client should be forced to depend on methods it does not use”.

Instead of using one big interface, split the big interface into many smaller and relevant interfaces, so that clients can know about the interfaces that are relevant to them.

The Interface Segregation Principle was first used and formulated by Robert C. Martin while consulting for Xerox.

Best example:

Consider the above example of Single Responsibility Principle (SRP).

We want to build an application that performs login and registration. Not only login and registration, it should send the verification mail to user who registered in. Also it should log the errors when errors occurs during login and registration attempt time.

This image has an empty alt attribute; its file name is image-2.png

Here we have one big interface IUser, look deeply how it is violating the Interface Segregation Principle (ISP),

But if you take a deep look into this methods, we can definitely figure out that, we don’t need to have LogError() and SendVerificationMail() part of IUser() interface.

Because User object is need to perform only for User Login and User Registration. But it should not be concerned about LogError() and SendVerificationMail().

We no need LogError() and SendVerificationMail() as part of IUser interface. We definitely need to segregate into separate interfaces.

Thus we made it,

This image has an empty alt attribute; its file name is image-3.png

In the above program you can see easily that, how nicely we segregated the interfaces to satisfy the Interface Segregation Principle (ISP).

5) Dependency Inversion Principle (DIP)

Dependency Inversion Principle ensures that, “High-level modules should not depend upon low-level modules, but both should depend upon Abstractions”.

It also states that, “Abstraction should not depend on details, details should depend upon abstractions“.

High level objects should not depend upon low level implementations.

Real time example:

Consider that, you are the CEO of E Commerce Giant Amazon, in that highest level position you only communicate with Business managers and other high level people. You won’t communicate with Amazon delivery boy.

As a CEO, your main job is to manage the organization and delegate responsibilities to the executive who report to you. If you spent time work with low-level work like making deliveries 9 AM to 6PM, you wouldn’t able to properly manage the company.

This is how Dependency Inversion Principle. High level objects should not depend upon low level implementations. High level code shouldn’t perform low level duties.

Reference :

Solid Design Principles

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s