Microsoft > CSharp >> Code Snippets Views : 2816
Rate This Article :

SOLID Principle

Introduction

Why SOLID Principle!? we will be able to develop applications with much Readable, Scalable, Productive and Maintainable Code by using its principle.

I wanted to share my knowledge on this principle with simple examples for better understanding. We can implement this principle in any programming language that supports OOAD. So No wait, here we go,

SOLID Principle

Software Design Pattern internally implements this “SOLID” principle to explain the “Creational, Structural and Behavioral” Patterns. SOLID explains 5 principles which will help us to create/achieve good software architecture. Below its ACRONYM,

S

Single Responsibility Principle

O

Open Closed Principle

L

LISKOV Substitution Principle

I

Interface Segregation Principle

D

Dependency Inversion Principle

 

1.      Single Responsibility Principle (SRP)

A Class should do one responsibility and there should be one reason to change the Class. It Means, if multiple responsibility given to the class, it will not be easily manageable/maintainable in some point of time.

Below find the example explaining the same.

namespace SolidPrinciple

{

    /// <summary>

    /// Order Detail

    /// </summary>

    public class OrderDetail

    {

        public string OrderId { get; set; }

        public string OrderDate { get; set; }

        public int OrderAmount { get; set; }

        public int UserId { get; set; }

    }

 

    public class OrderDataManager

    {

        /// <summary>

        /// Submit Order Details into Database

        /// </summary>

        /// <param name="orderDetail">Order Information</param>

        /// <returns>Order Information Saved Or Not</returns>

        public bool SaveOrderDetails(OrderDetail orderDetail)

        {

            // Save Order Details into Database.

            return true;

        }

 

        /// <summary>

        /// To Generate Report For Orders

        /// </summary>

        /// <param name="orderDetail">Order Information</param>

        public void GenerateOrderReport(OrderDetail orderDetail, string formatType)

        {

            // Generate Order Report.

        }

 

        /// <summary>

        /// Send Mail To Users About Orders

        /// </summary>

        /// <param name="orderDetail"></param>

        public void SendMailToUser(OrderDetail orderDetail)

        {

            // Send Mail to Users about the Placed Order.

        }

    }

}

 

 

OrderDataManager” Class takes too many responsibilities like “Saving of Order Details”, “GenerateOrderReport” and “SendMailToUser”. This class will be modified if there is any logic change in any of the three methods in future and this violates the “Single Responsibility Principle”. To follow “SRP”, it is good to divide the above class into three different classes based on its Responsibility. Please refer below,

 

namespace SolidPrinciple

{

    /// <summary>

    /// Order Detail

    /// </summary>

    public class OrderDetail

    {

        public string OrderId { get; set; }

        public string OrderDate { get; set; }

        public int OrderAmount { get; set; }

        public int UserId { get; set; }

    }

 

    public class OrderDataManager

    {

        /// <summary>

        /// Submit Order Details into Database

        /// </summary>

        /// <param name="orderDetail">Order Information</param>

        /// <returns>Order Information Saved Or Not</returns>

        public bool SaveOrderDetails(OrderDetail orderDetail)

        {

            // Save Order Details into Database.

            return true;

        }

    }

 

    public class ReportGeneration

    {

        /// <summary>

        /// Report Format Type

        /// </summary>

        public string FormatType { get; set; }

 

        /// <summary>

        /// To Generate Report For Orders

        /// </summary>

        /// <param name="orderDetail">Order Information</param>

        public void GenerateOrderReport(OrderDetail orderDetail)

        {

            // Generate Order Report.

        }

    }

 

    public class NotificationManagement

    {

        /// <summary>

        /// Send Mail To Users About Orders

        /// </summary>

        /// <param name="orderDetail"></param>

        public void SendMailToUser(OrderDetail orderDetail)

        {

            // Send Mail to Users about the Placed Order.

        }

    }

}

 

Now we have divided the responsibilities by creating three different classes. Changing of Logic in one class will not affect other class methods and now all classes holds Single Responsibility Principle!

 

2.       Open Closed Principle

This principle states that “A Class should be open for extension but Closed for Modification”. Let’s look at the same class “ReportGeneration”.

public class ReportGeneration

    {

        /// <summary>

        /// Report Format Type

        /// </summary>

        public string FormatType { get; set; }

 

        /// <summary>

        /// To Generate Report For Orders

        /// </summary>

        /// <param name="orderDetail">Order Information</param>

        public void GenerateOrderReport(OrderDetail orderDetail)

        {

            if (FormatType == "PDF")

            {

                // Generated PDF Document and Place it in Share Path.

            }

            else if (FormatType == "Excel")

            {

                // Generated Excel Document and Place it in Share Path.

            }

        }

    }

Above mentioned class method named “GenerateOrderReport” generates reports in PDF and Excel formats. If we need to add another report format (for e.g. CSV) this method needs to be modified by adding another “else – if” for CSV report. Due to this Maintainability of this method will affect in future. In order to improve its maintainability, we need use “OCP” principle as mentioned below.

We can create abstract class to hold common details (or) Interface to adopt the “OCP” Principle. Refer class modification below.

public abstract class ReportGenerationBase

    {

        public virtual void GenerateOrderReport(OrderDetail orderDetail)

        {

            //Generate Report in Excel Format

        }

    }

 

    public class ReportGenerationInPDF : ReportGenerationBase

    {

        /// <summary>

        /// To Generate Report For Orders

        /// </summary>

        /// <param name="orderDetail">Order Information</param>

        public override void GenerateOrderReport(OrderDetail orderDetail)

        {

            //Generate Report in PDF Format

        }

    }

 

    public class ReportGenerationInCSV : ReportGenerationBase

    {

        /// <summary>

        /// To Generate Report For Orders

        /// </summary>

        /// <param name="orderDetail">Order Information</param>

        public override void GenerateOrderReport(OrderDetail orderDetail)

        {

            //Generate Report in CSV Format

        }

    }

With the above changes, without disturbing the abstract class “ReportGenerationBase”, we will be able to add a new class for a new report type in future with additional functionalities. So here “ReportGenerationBase” class holds the “OCP” principle, i.e. A Class should be Open for Extension and Closed for Modification.

 

3.      Liskov Substitution Principle

This Principle is very simple but very important to understand. Let’s try to understand the below points before getting into Code.

1.       Functions/Methods that use Pointers (or) References to the base classes must be able to use derived classes without knowing it. (Reference - I Recently read it in one of the article that fits very well here)

2.       Derived class should not break the Base class type definition and Behavior.

Refer the below code, it violates the 2nd Point which is mentioned above.

public abstract class ReportGenerationBase

    {

        public virtual void GenerateOrderReport(OrderDetail orderDetail)

        {

            //Generate Report In Excel Format

        }

 

        // Generate User Report

        public abstract void GenerateUserReport(int userId);

    }

 

    public class ReportGenerationInPDF : ReportGenerationBase

    {

        /// <summary>

        /// To Generate Report For Orders

        /// </summary>

        /// <param name="orderDetail">Order Information</param>

        public override void GenerateOrderReport(OrderDetail orderDetail)

        {

            //Generate Report In PDF Format.

        }

 

        public override void GenerateUserReport(int userId)

        {

            //Generate User Report in PDF Format.

        }

    }

 

    public class ReportGenerationInCSV : ReportGenerationBase

    {

        /// <summary>

        /// To Generate Report For Orders

        /// </summary>

        /// <param name="orderDetail">Order Information</param>

        public override void GenerateOrderReport(OrderDetail orderDetail)

        {

            //Generate Report In CSV Format

        }

 

        public override void GenerateUserReport(int userId)

        {

            throw new NotImplementedException();

        }

    }

ReportGenerationInCSV is not implemented the base class method and the below code will throw error during run time and this violates the “LISKOV Substitution Principle”.

public class ReportExecution

    {

        public void GenerateReport(OrderDetail orderDetail)

        {

            List<ReportGenerationBase> reportGenerationList = new List<ReportGenerationBase>();

            reportGenerationList.Add(new ReportGenerationInPDF());

            reportGenerationList.Add(new ReportGenerationInCSV());

 

            foreach (var report in reportGenerationList)

            {

                report.GenerateOrderReport(orderDetail);

 

                // This will throw Exception for the method present in "ReportGenerationInCSV".

                report.GenerateUserReport(orderDetail);

            }

        }

    }

 

So how to rectify it?! We need to segregate the responsibilities from the Abstract class we have used above.

NowIOrderReportGenerationBase” and “IUserReportGenerationBase” has introduced segregating its responsibilities to generate report for “Order” and “User”.

public interface IOrderReportGenerationBase

    {

        void GenerateOrderReport(OrderDetail orderDetail);

    }

 

public interface IUserReportGenerationBase

    {

        void GenerateUserReport(OrderDetail orderDetail);

    }

 

public class ReportGenerationInPDF : IOrderReportGenerationBase, IUserReportGenerationBase

    {

        /// <summary>

        /// To Generate Report For Orders

        /// </summary>

        /// <param name="orderDetail">Order Information</param>

        public void GenerateOrderReport(OrderDetail orderDetail)

        {

            //Generate Report In PDF Format.

        }

 

        public void GenerateUserReport(OrderDetail orderDetail)

        {

            //Generate User Report in PDF Format.

        }

    }

 

      public class ReportGenerationInCSV : IOrderReportGenerationBase

    {

        /// <summary>

        /// To Generate Report For Orders

        /// </summary>

        /// <param name="orderDetail">Order Information</param>

        public void GenerateOrderReport(OrderDetail orderDetail)

        {

            //Generate Report In CSV Format

        }

    }

 

public class ReportExecution

    {

        public void GenerateReport(OrderDetail orderDetail)

        {

            List<IOrderReportGenerationBase> orderReportGenerationList = new List<IOrderReportGenerationBase>();

            orderReportGenerationList.Add(new ReportGenerationInPDF());

            orderReportGenerationList.Add(new ReportGenerationInCSV());

 

            foreach (var report in orderReportGenerationList)

            {

                report.GenerateOrderReport(orderDetail);

            }

 

            List<IUserReportGenerationBase> userReportGenerationList = new List<IUserReportGenerationBase>();

            userReportGenerationList.Add(new ReportGenerationInPDF());

 

            foreach (var report in userReportGenerationList)

            {

                report.GenerateUserReport(orderDetail);

            }

        }

    }

Now there will not be any issue in generating the report for “Order” and “User”. Now this design implements the “LSP” using “Interface Segregation Principle”.

4.      Interface Segregation Principle

This Principle States that “A Client shouldn’t be forced to use the Interfaces which they don’t want it”. A Big interface can be split into small interfaces combined with group of methods which are serving single responsibility.

Consider the below scenario,

public interface IUser

    {

        void PlaceOrder(OrderDetail orderDetail);

 

        bool ApproveOrRejectOrder(OrderDetail orderDetail);

    }

 

    public class Admin : IUser

    {

        public void PlaceOrder(OrderDetail orderDetail)

        {

            //Place Order

        }

 

        public bool ApproveOrRejectOrder(OrderDetail orderDetail)

        {

            // Approve Or Reject Order

            return true;

        }

    }

 

 

Consider the above method, system is now introduced with a new user called “Seller” to Approve (or) Reject their received orders. Seller only can do “Approve/Reject” the received orders to their shop.

 

Can we re-use the above interface IUser for Seller?

 

  public class Seller : IUser

    {

        public void PlaceOrder(OrderDetail orderDetail)

        {

            throw new Exception("Seller Can't place order in the system");

        }

 

        public bool ApproveOrRejectOrder(OrderDetail orderDetail)

        {

            // Approve Or Reject their received Order

            return true;

        }

    }

 

Now what’s the flaw here? Now “IUser” interface forces the “Seller” class to implement the “PlaceOrder” method. This violates the “Interface segregation Principle”.

 

Then how to correct it? Now we have three types of users.

 

1.       User – User can “Place” the Orders

2.       Seller – Seller only can “Approve(or) Reject” the received Orders to their shop

3.       Admin – Admin only “Place/Approve/Reject” orders

 

To correct the above design, create interfaces by segregating based on the responsibilities using “SRP” principle.

 

            public interface IUser

    {

        void PlaceOrder(OrderDetail orderDetail);

    }

 

    public interface ISeller

    {

        bool ApproveOrRejectOrder(OrderDetail orderDetail);

    }

 

    public class User : IUser

    {

        public void PlaceOrder(OrderDetail orderDetail)

        {

            //Place Order

        }

    }

 

    public class Seller : ISeller

    {

        public bool ApproveOrRejectOrder(OrderDetail orderDetail)

        {

            // Approve Or Reject Order

            return true;

        }

    }

 

    public class Admin : IUser, ISeller

    {

        public void PlaceOrder(OrderDetail orderDetail)

        {

            //Place Order

        }

 

        public bool ApproveOrRejectOrder(OrderDetail orderDetail)

        {

            // Approve Or Reject Order

            return true;

        }

    }

 

 

5.      Dependency Inversion Principle

This Principle states that “High Level Class shouldn’t depend on Low Level Class”. If Low Level Class is tightly coupled with High level Class, then change in the Low Level Class will affect the High Level class which is using it.

 

From the below code, after placing order by the “User” class, system has to send mail to user about the order.

 

public class User : IUser

    {

        OrderDataManagement orderManagement = null;

        NotificationManagement notificationManagement = null;

 

        public User()

        {

            orderManagement = new OrderDataManagement();

 

            notificationManagement = new NotificationManagement();

        }

 

        public void PlaceOrder(OrderDetail orderDetail)

        {

            // Place Order

            orderManagement.SaveOrderDetails(orderDetail);

 

            // Send Mail To User After order.

            notificationManagement.SendMailToUser(orderDetail);

        }

    }

 

    public class OrderDataManagement

    {

        /// <summary>

        /// Submit Order Details into Database

        /// </summary>

        /// <param name="orderDetail">Order Information</param>

        /// <returns>Order Information Saved Or Not</returns>

        public bool SaveOrderDetails(OrderDetail orderDetail)

        {

            // Save Order Details into Database.

            return true;

        }

    }

 

    public class NotificationManagement

    {

        /// <summary>

        /// Send Mail  To Users About Orders

        /// </summary>

        /// <param name="orderDetail"></param>

        public void SendMailToUser(OrderDetail orderDetail)

        {

            // Send Mail to Users about the Placed Order.

        }

    }

 

From the above, if “User” class want to send information through SMS instead of Mail, Both “NotificationManagement” and “User” class has to be modified to add new method to send SMS. Now “User” class is completely depending on “NotificationManagement” class.

 

So how to change it?? Here we go,

 

Now introduced an interface and implementing its method in both “MailNotification” and “SMSNotification” will resolve this issue.

 

Is this correct?! No, please read on.

 

public class User : IUser

    {

        OrderDataManagement orderManagement = null;

        INotificationManagement notificationManagement = null;

 

        public User()

        {

            orderManagement = new OrderDataManagement();

 

            notificationManagement = new SMSNotification();

        }

 

        public void PlaceOrder(OrderDetail orderDetail)

        {

            // Place Order

            orderManagement.SaveOrderDetails(orderDetail);

 

            // Send Notification To User After order.

            notificationManagement.SendMessage(orderDetail);

        }

    }

 

    public interface INotificationManagement

    {

        void SendMessage(OrderDetail orderDetail);

    }

 

    public class MailNotification: INotificationManagement

    {

        /// <summary>

        /// Send Mail  To Users About Orders

        /// </summary>

        /// <param name="orderDetail"></param>

        public void SendMessage(OrderDetail orderDetail)

        {

            // Send Mail to Users about the Placed Order.

        }

    }

 

    public class SMSNotification : INotificationManagement

    {

        /// <summary>

        /// Send SMS  To Users About Orders

        /// </summary>

        /// <param name="orderDetail"></param>

        public void SendMessage(OrderDetail orderDetail)

        {

            // Send SMS to Users about the Placed Order.

        }

    }

 

If you see “User” class is tightly coupled (or) completely dependent on “SMSNotification” class.

 

Now we need to remove its dependency by using “Dependency Injection” Principle to make the classes loosely coupled with other class.

 

Now-a-days in most of the application implements the Dependency Injection Framework to create an instance of an object and to define its life time. There are three Types of Dependency Injections.

 

1.       Constructor Injection

2.       Property Injection

3.       Method Injection

1.      Constructor Injection

Now we have removed the dependency by injecting “SMSNotification” (or) “MailNotification” as a constructor parameter of type “INotificationManagement” interface. Now the class is loosely coupled.

 

In future, we no need to make any changes when there is any new notification methodology is added in the project.

 

public class User : IUser

    {

        OrderDataManagement orderManagement = null;

        INotificationManagement notificationManagement = null;

 

        public User(INotificationManagement notificationObject)

        {

            orderManagement = new OrderDataManagement();

 

            notificationManagement = notificationObject;

        }

 

        public void PlaceOrder(OrderDetail orderDetail)

        {

            // Place Order

            orderManagement.SaveOrderDetails(orderDetail);

 

            // Send Notification To User After order.

            notificationManagement.SendMessage(orderDetail);

        }

    } 

2.     Property Injection

Now “User” class has added with additional Property “NotificationManagement” exposing it to outer world to set it with the Appropriate Notification Mechanism (Mail/Message)


public class User : IUser

    {

        OrderDataManagement orderManagement = null;

 

        public INotificationManagement NotificationManagement { private get; set; }

 

        public User()

        {

            orderManagement = new OrderDataManagement();

        }

 

        public void PlaceOrder(OrderDetail orderDetail)

        {

            // Place Order

            orderManagement.SaveOrderDetails(orderDetail);

 

            // Send Notification To User After order.

            if (NotificationManagement != null)

                NotificationManagement.SendMessage(orderDetail);

        }

    }

3.     Method Injection

Modifying the current method “PlaceOrder” to accept “INotificationManagement” object as a parameter. By doing this, interface “INotificationManagement” parameter will work without knowing its derived class (Mail/Message).


public interface IUser

    {

        void PlaceOrder(OrderDetail orderDetail, INotificationManagement notificationManagement);

    }

 

public class User : IUser

    {

        OrderDataManagement orderManagement = null;

 

        public User()

        {

            orderManagement = new OrderDataManagement();

        }

 

        public void PlaceOrder(OrderDetail orderDetail, INotificationManagement notificationManagement)

        {

            // Place Order

            orderManagement.SaveOrderDetails(orderDetail);

 

            // Send Notification To User After order.

            if (notificationManagement != null)

                notificationManagement.SendMessage(orderDetail);

        }

    }

 

 

With this we came to an end of this big Article!! Now we understand that “SOLID” principle helps us to write a Readable, Scalable and Maintainable code.

About Author
Raj Kumar
Total Posts 55
Developer in .Net!
Comment this article
Name*
Email Address* (Will not be shown on this website.)
Comments*
Enter Image Text*
   
View All Comments
Comments not posted yet!! Please post your comments on this article.
  Privacy   Terms Of Use   Contact Us
© 2016 Developerin.Net. All rights reserved.
Trademarks and Article Images mentioned in this site may belongs to Microsoft and other respective trademark owners.
Articles, Tutorials and all other content offered here is for educational purpose only and its author copyrights.