Sunday, August 30, 2015

SOLID Object Oriented Design Principles


SOLID Overview


Software design principles are a set of guidelines that helps developers to make a good system design. The most important principle is SOLID principle. SOLID is a set of five guiding principles that help developers design objects that are easy to maintain, extend and use. Please note that SOLID is a guide not the goal.

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) 

Single responsibility principle (SRP)

Real world comparison



SRP says "Every software module should have one and only one reason to change".Software Module – Class, Function etc.

Identify Problem in Programming


Problems

  1. Every time insert logic changes, this class will change. 
  2. Every time report format changes, this class will changes. 
  3. A single change leads to double testing (or maybe more).

Solutions which will not Violate SRP

We can create three different classes

  1. Employee – Contains Properties (Data)
  2. EmployeeDB – Does database operations
  3. EmplyeeReport – Does report related tasks



    public class Employee
    {
        public string EmployeeName { get; set; }
        public int EmployeeNo { get; set; }
    }

    public class EmployeeDB
    {
        public void Insert(Employee e)
        {
            //Database Logic written here
        }
        public Employee Select()
        {
            //Database Logic written here
        }
    }

    public class EmployeeReport
    {
        public void GenerateReport(Employee e)
        {
            //Set report formatting
        }
    }


SRP says that a class should have only one responsibility and not multiple.


Open/closed principle (OCP)

Real World Comparison








Let’s assume you want to add one more floor between the first and second floor in your two floor house. Do you think it is possible? Yes it is, but is it feasible? Here are some options:

  • One thing you could have done at time you were building the house first time was make it with three floors, keeping second floor empty. Then utilize the second floor anytime you want. I don’t know how feasible that is, but it is one solution.
  • Break the current second floor and build two new floors, which is not sensible.


OCP says, "Software modules(classes, modules, functions, etc.) should be open for extensions and closed for modifications."

Identify Problem in Programming - Customer Discount 

    class Customer
    {
        public String CustomerName { get; set; }
        public double Amount { get; set; }
        public CustType CustomerType { get; set; }

        public enum CustType
        {
            Free,
            Paid,
            Gold
        }

        public double Discount
        {
            get
            {
                if (CustomerType == CustType.Free)
                {
                    return 0;
                }
                else if (CustomerType == CustType.Gold)
                {
                    return Amount * 0.2;
                }
                else
                {
                    return Amount * 0.1;
                }

            }
        }
    }


The problem is if we add a new customer type we need to go and add one more “IF” condition in the “Discount” property, in other words we have to change the customer class.

If we are changing the customer class again and again, we need to ensure that the previous conditions with new one’s are tested again , existing client’s which are referencing this class are working properly as before.



End Goal

The new solution should insure that the current class is not modified for Adding new customer types. 
The solution should adhere to SRP i.e. Single Responsibility Principle


    public abstract class CustomerAbstract


    {
        public string CustomerName { get; set; }
        public double Amount { get; set; }
        public abstract double Discount { get; }
    }

    public class FreeCustomer : CustomerAbstract
    {
        public override double Discount
        {
            get
            {
                return 0;
            }
        }
    }

    public class GoldCustomer : CustomerAbstract
    {
        public override double Discount
        {
            get
            {
                return Amount * 0.2;
            }
        }
    }


Liskov substitution principle (LSP)

We must make sure that new derived classes are extending the base classes without changing their behaviour.

Identify Problem in Programming

    public class Rectangle
    {
        protected int _width;
        protected int _height;

        public int Width
        {
            get { return _width; }
        }

        public int Height
        {
            get { return _height; }
        }

        public virtual void SetWidth(int width)
        {
            _width = width;
        }

        public virtual void SetHeight(int height)
        {
            _height = height;
        }
    }


    public class Square : Rectangle
    {
        public override void SetWidth(int width)
        {
            _width = width;
            _height = width;
        }

        public override void SetHeight(int height)
        {
            _height = height;
            _width = height;
        }
    }

    public class RectangleTests
    {
        public void AreaOfRectangle()
        {
            Rectangle r = new Square();
            r.SetWidth(5);

            r.SetHeight(2);
            Assert.IsEqual(r.Width * r.Height, 10);
        }
    }

As per the LSP, we should be able to replace Rectangle with Squire but in above example we can’t do this. We are changing the behavior of SetWidth and SetHeight method in drive class (For Rectangle height and width cannot be equal, if they are equal it’s cannot be Rectangle).LSP says that new derived classes should extend the base classes without changing their behaviorhence this solution is violating the LSP.

Solutions which will not Violate LSP

    public abstract class Shape
    {
        public virtual int Height { get; set; }
        public virtual int Width { get; set; }
    }

    public class Rectangle : Shape
    {
    }

    public class Square : Shape
    {
        public override int Height
        {
            get { return base.Height; }
            set { SetWidthAndHeight(value); }
        }

        public override int Width
        {
            get { return base.Width; }
            set { SetWidthAndHeight(value); }
        }

        private void SetWidthAndHeight(int value)
        {
            base.Height = value;
            base.Width = value;
        }
    }

    public class TestClass
    {
        public void TestMethod()
        {
            Shape rectangle = new Rectangle();
            rectangle.Width = 5;
            rectangle.Height = 2;

            Shape square = new Square();
            square.Width = 5;
            square.Height = 2;

            Console.WriteLine("Rectangle: {0} x {1}", rectangle.Height, rectangle.Width);
            Console.WriteLine("Square: {0} x {1}", square.Height, square.Width);
            Console.ReadLine();
        }
    }


Interface segregation principle (ISP)
ISP says “Clients should not be forced to depend upon interfaces that they do not use”

Identify Problem in Programming





    public interface ICustomer
    {
        string CustomerName{ get; set; }
        double Amount { get; set; }
        string Print();
    }

    public abstract class CustomerAbstract : ICustomer
    {
        public string CustomerName { get; set; }
        public double Amount { get; set; }
        public abstract string Print();
    }

    public class GoldCustomer : CustomerAbstract
    {

        public override string Print()
        {
            // print gold customer
        }
    }

    public class PaidCustomer : CustomerAbstract
    {

        public override string Print()
        {
           // print paid customer
        }
    }


    public class FreeCustomer : CustomerAbstract
    {

        public override string Print()
        {
            throw new NotImplementedException();
        }
    }


FreeCustomer class is forced to implement the interface and it doesn’t require to have print method.




Solutions which will not Violate ISP

    public interface ICustomer
    {
        string CustomerName{ get; set; }
        double Amount { get; set; }
    }

    public interface IPrintCustomer : ICustomer
    {
        string Print();
    }

    public abstract class CustomerAbstract : ICustomer
    {
        public string CustomerName { get; set; }
        public double Amount { get; set; }
    }


    public class GoldCustomer : CustomerAbstract, IPrintCustomer
    {
        public string Print()
        {
            // print gold customer
            return "Gold Customer name :" + this.CustomerName;
        }
    }


    public class PaidCustomer : CustomerAbstract, IPrintCustomer
    {
        public string Print()
        {
            // print gold customer
            return "Paid Customer name :" + this.CustomerName;
        }
    }


    public class FreeCustomer : CustomerAbstract
    {

    }


Dependency inversion principle (DIP)

Dependency inversion principle is a software design principle which provides us the guidelines to write loosely coupled classes.



Real World Comparison

Let’s talk an example of desktop computers. Different parts such as RAM, a hard disk, and CD-ROM (etc.) are loosely connected to the motherboard. That means, in future if any part stops working it can easily be replaced with a new one. Just imagine a situation where all parts were tightly coupled to each other, which means it would not be possible to remove any part from the motherboard. Then in that case if the RAM stops working we have to buy new motherboard which is going to be very expensive.

Identify Problem in Programming

public class CustomerOperation
    {
        public void Insert(Customer c)
        {
            try
            {
                //Insert logic
            }
            catch (Exception e)
            {
                FileLogger f = new FileLogger();
                f.LogError(e);
            }
        }
    }

    public class FileLogger
    {
        public void LogError(Exception e)
        {
            //Log Error in a physical file
        }
    }

In the above code CustomerOperation class is directly dependent on the FileLogger class which will log exceptions in physical file. Now let’s assume tomorrow management decides to log exceptions in the Event Viewer. For that we have to change the existing code and that might create a new error.

Solutions which will not Violate DIP

    public interface ILogger
    {
        void LogError(Exception e);
    }

    public class FileLogger : ILogger
    {
        public void LogError(Exception e)
        {
            //Log Error in a physical file
        }
    }

    public class EventViewerLogger : ILogger
    {
        public void LogError(Exception e)
        {
            //Log Error in a physical file
        }
    }

    public class CustomerOperation
    {
        private ILogger _objLogger;
        public CustomerOperation(ILogger objLogger)
        {
            _objLogger = objLogger;
        }

        public void Insert(Customer c)
        {
            try
            {
                //Insert logic
            }
            catch (Exception e)
            {
                _objLogger.LogError(e);
            }
        }
    }

As you can see the client depends on ILogger which can be set to an instance of any derived class.

Dependency Injection
Dependency Injection is mainly for injecting the concrete implementation into a class that is using abstraction i.e. interface inside. The main idea of dependency injection is to reduce the coupling between classes and move the binding of abstraction and concrete implementation out of the dependent class.

Dependency injection can be done in three ways.

  • Constructor injection 
  • Method injection 
  • Property injection

Constructor Injection

    public interface INotifyAction
    {
        void ActionOnNotification(string message);
    }

    class AppPoolWatcher
    {
        INotifyAction action = null;

        public AppPoolWatcher(INotifyAction implementation)
        {
            this.action = implementation;
        }

        public void Notify(string message)
        {
            action.ActionOnNotification(message);
        }
    }

Method Injection

    public interface INotifyAction
    {
        void ActionOnNotification(string message);
    }

    class AppPoolWatcher
    {
        INotifyAction action = null;

        public void Notify(INotifyAction implementation, string message)
        {
            this.action = implementation;
            action.ActionOnNotification(message);
        }
    }

Property Injection

    public interface INotifyAction
    {
        void ActionOnNotification(string message);
    }

    class AppPoolWatcher
    {
        INotifyAction action = null;

        public INotifyAction Action
        {
            get
            {
                return action;
            }

            set
            {
                action = value;
            }
        }

        public void Notify(string message)
        {           
            action.ActionOnNotification(message);
        }
    }