SOLID Principles with C#

SOLID Principles are coding standards that all developers should have a clear concept for developing software in a proper way to avoid a bad design.

S - Single Responsibility Principle (SRP): A class should have one, and only one, a reason to change

In Single Responsibility Principle
  1. Each class and module should focus on a single task at a time
  2. Everything in the class should be related to that single purpose
  3. There can be many members in the class as long as they related to the single responsibility
  4. With SRP, classes become smaller and cleaner
  5. Code is less fragile 
Here UserAccount class created which have below operations like, Login, Register, LogError and Send Mail.
According to SRP, ideally, LogError and SendEmail are should not be part of UserAccount operations. 
    public class UserAccount
    {
        public bool Login(string username, string password)
        {
            //Login varification code
            return true;
        }
        public bool Register(string username, string password, string email)
        {
            //Register Logic goes here...
            return true;
        }
        public void LogError(string error)
        {
            //Logging Operation...
        }
        public bool SendEmail(string emailContent)
        {
            //Send Email Code
            return true;
        }
    }

After applying SRP, the Class structure should look like as defined below:
    public class UserAccount
    {
        public bool Login(string username, string password)
        {
            //Login varification code
            return true;
        }
        public bool Register(string username, string password, string email)
        {
            //Register Logic goes here...
            return true;
        }
    }

    public class Logger
    {
        public void LogError(string error)
        {
            //Logging Operation...
        }
    }

    public class MailActivity
    {
        public bool SendEmail(string emailContent)
        {
            //Send Email Code
            return true;
        }
    }

O - Open-closed Principle (OCP): Entities should be open for extension but closed for modification. In object-oriented programming, the open/closed principle states that "software entities such as classes, modules, functions, etc. should be open for extension, but closed for modification"

The code below might seem to be good. However, if we wanted to offer discounts to other types of creditcard, required to modify the existing code to accomplish it.

For example,
if we wanted to offer specific discounts to other creditcard types, we’ll have to do so by adding more if statements to the GetDiscount(double monthlyCost) method in the below example.
    class CreditCard
    {
        public int CreditCardType { get; set; }

        public double GetDiscount(double monthlyCost)
        {
            if (CreditCardType == 1)
            {
                return monthlyCost * 0.05;
            }
            else
            {
                return monthlyCost * 0.01;
            }
        }
    }

By modifying the existing code we’ll be breaking the Open/Closed principle. Open Closed principle helps you write code that is extensible and cleaner.
    class CreditCard
    {
        public virtual double GetDiscount(double monthlyCost)
        {
            return 0.0;
        }
    }

    class SilverCreditCard : CreditCard
    {
        public override double GetDiscount(double monthlyCost)
        {
            return monthlyCost * 0.01;
        }
    }

    class GoldCreditCard : CreditCard
    {
        public override double GetDiscount(double monthlyCost)
        {
            return monthlyCost * 0.02;
        }
    }

    class PlatinumCreditCard : CreditCard
    {
        public override double GetDiscount(double monthlyCost)
        {
            return monthlyCost * 0.05;
        }
    }

L - Liskov Substitution Principle (LSP): The Liskov Substitution principle was introduced by Barbara Liskov in her conference keynote “Data abstraction” in 1987. A parent class object should be able to refer child objects seamlessly during runtime polymorphism.

Now, check the below code and it will violate the LSP principle. while setting rating using e.SetRating(1); method for all employee, It will throw an exception where TeamLead does not have SetRating method is implemented.
    public abstract class Employee
    {
        public virtual string GetRating(int employeeId)
        {
            return "Base : Get Rating";
        }
        public virtual string SetRating(int employeeId)
        {
            return "Base : Set Rating";
        }
    }
    public class Manager : Employee
    {
        public override string GetRating(int employeeId)
        {
            return "Manager :  Get Rating";
        }
     // May be for TeamLead employee we do not need to Set Rating details into database.
        public override string SetRating(int employeeId)
        {
            return "Manager : Set Rating";
        }
    }
    public class TeamLead : Employee
    {
        public override string GetRating(int employeeId)
        {
            return "TeamLead :  Get Rating";
        }
     // May be for TeamLead employee we do not need to Set Rating details into database.
        public override string SetRating(int employeeId)
        {
            throw new NotImplementedException();
        }
    }

    public class EmployeeRatingCalculation
    {
        public void SetEmployeeRating()
        {
            List<Employee> employeeList = new List<Employee>();
            employeeList.Add(new TeamLead());
            employeeList.Add(new Manager());
            foreach (Employee e in employeeList)
            {
                e.SetRating(1);
            }
        }
    }

A solution here is breaking the whole thing in 2 different interfaces, 1. ITeamLead 2. IManager and implement according to Employee type.

    public interface ITeamLead
    {
        string GetRating(int employeeId);
    }

    public interface IManager
    {
        string SetRating(int employeeId);
    }

I - Interface Segregation Principle (ISP): A Client should not be forced to implement an interface that it doesn’t use. Which means, Instead of one fat interface many small interfaces are preferred based on groups of methods with each one serving one submodule.

Here IEmployee, have set of operation for all Employee, however GenerateSalary() operation limited to HREmployee, However by using IEmployee implementing an interface for Employee class, It is forcing Employee class to implement a method which should not be part of Employee class operation.
    public interface IEmployee
    {
        void GetSalaryDetail();
        void GetPersonalDetails();
        void GenerateSalary();
    }

    public class HREmployee : IEmployee
    {
        public void GetSalaryDetail()
        {
            //Code to assign a task. 
        }
        public void GetPersonalDetails()
        {
            //Code to create a sub task 
        }
        public void GenerateSalary()
        {
            //Code to implement perform assigned task. 
        }
    }

    public class Employee : IEmployee
    {
        public void GetSalaryDetail()
        {
            //Code to assign a task. 
        }
        public void GetPersonalDetails()
        {
            //Code to create a sub task 
        }
        public void GenerateSalary()
        {
            throw new Exception("Employee can't Generate Salary");
        }
    }

After applying ISP, Implement dedicated for IHREmployee related operation and Employee class does not need to implement HR related operations like GenerateSalary().
    public interface IEmployee
    {
        void GetSalaryDetail();
        void GetPersonalDetails();
    }

    public interface IHREmployee
    {
        void GenerateSalary();
    }

    public class HREmployee : IEmployee, IHREmployee
    {
        public void GetSalaryDetail()
        {
            //Code to assign a task. 
        }
        public void GetPersonalDetails()
        {
            //Code to create a sub task 
        }
        public void GenerateSalary()
        {
            //Code to implement perform assigned task. 
        }
    }

    public class Employee : IEmployee
    {
        public void GetSalaryDetail()
        {
            //Code to assign a task. 
        }
        public void GetPersonalDetails()
        {
            //Code to create a sub task 
        }
    }


D - Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules. Both should depend on abstractions, Abstractions should not depend on details. Details should depend on abstractions.

Let undestand DIP with Logger functionality. Now ErrorLogger class totally depends on Logger class, because it only writes one type of log that is FileLog. If we want to introduce any other like Database Log then? We need to change the ErrorLogger system also. And this is called tightly coupled.
    public class Logger
    {
        public void Log()
        {
            // code to Log
        }
    }

    public class ErrorLogger
    {
        private Logger logger;
        public ErrorLogger()
        {
            logger = new Logger();
        }

        public void WriteLogInFile()
        {
            logger.Log();
        }
    }

By modify our code as below, still ErrorLogger class depends on FileLog class.
Have look on below code.
    public interface ILogger
    {
        void Log();
    }
    public class FileLog : ILogger
    {
        public void Log()
        {
            // code to log into File
        }
    }

    public class DatabseLog : ILogger
    {
        public void Log()
        {
            // code to log into Database
        }
    }
    public class ErrorLogger
    {
        private ILogger iLogger;
        public ErrorLogger()
        {
            iLogger = new FileLog();
        }
        public void DoLog()
        {
            iLogger.Log();
        }
    }

Now, we can use dependency injection so that we can make it loosely coupled.
There are 3 types to DI, Either of  DI code to be implemented to achieve DI principle
 - Constructor injection
 - Property injection
 - Method injection

    // Constructor Injection
    public class ErrorLogger
    {
        private ILogger iLogger;
        public ErrorLogger(ILogger logger)
        {
            iLogger = logger;
        }
        public void DoLog()
        {
            iLogger.Log();
        }
    }
 --------------------- OR -----------------------

    //Property Injection
    public class ErrorLogger
    {
        public ILogger iLogger { private get; set; }
        public ErrorLogger()
        {
        }
        public void DoLog()
        {
            iLogger.Log();
        }
    }
 --------------------- OR -----------------------

    //Method Injection
    public class Notification
    {
        public void DoLog(ILogger iLogger)
        {
            iLogger.Log();
        }
    }

SOLID Principle  Advantages:
Maintainability
Testability
Flexibility & Extensibility
Parallel Development'
Loose Coupling

So, the SOLID principle will help us to write loosely coupled code which is highly maintainable and Extensible development.


Happy Codding😊👍


Comments

Hardik Raval said…
Nice post..though I'm a java developer I can pretty much relate this post in terms of java programming.
Ujala said…
Nice post with good explanations
CAROLINE JAMES said…
I really want to thank you for such a nice blog post that helped me to understand why it is important. Best ASP.net Development Service

Popular posts from this blog

Overview of MVC

MVC Request Execution Stages - Life Cycle

Introduction to ASP.Net MVC

ASP.NET MVC: Benefits

Paged Data Source - Custom Paging

ASP.NET MVC Version History

Managed Code and UnManaged Code

Auto Sequence Number in Grid Control with Paging