Tuesday, 27 March 2018

Solid Principles



Solid Principles in Programming


In object-oriented computer programming, the term SOLID is a mnemonic acronym for five design principles intended to make software designs more understandable, flexible and maintainable. 

Single responsibility principle
class should have only a single responsibility (i.e. changes to only one part of the software's specification should be able to affect the specification of the class).

Open/closed principle
"Software entities … should be open for extension, but closed for modification."

Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program." See also design by contract.

Interface segregation principle
Many client-specific interfaces are better than one general-purpose interface.

Dependency inversion principle
One should "depend upon abstractions, [not] concretions.”


Single responsibility principle example:(Without SRP)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Solid_Principles
{
    class Program
    {
        static void Main(string[] args)
        {
            Customer customer = new Customer();
            customer.Add();
            Console.ReadLine();
        }
    }

    public class Customer
    {
        public void Add()
        {
            HandleError handleError = new HandleError();
            try
            {
                //Customer Data Access Code Here;
            }
            catch (Exception error)
            {
                System.IO.File.WriteAllText(@"c:\test\error.txt", error.ToString());
            }
            finally
            {
            }
        }
    }
}

Single responsibility principle example:(With  SRP)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Solid_Principles
{
    class Program
    {
        static void Main(string[] args)
        {
            Customer customer = new Customer();
            customer.Add();
            Console.ReadLine();
        }
    }

    public class Customer
    {
        public void Add()
        {
            HandleError handleError = new HandleError();
            try
            {
                //Customer Data Access Code Here;

            }
            catch (Exception error)
            {
                handleError.LogError(error);
            }
            finally
            {
            }
        }
    }
}
public class HandleError
{
    public void LogError(Exception error)
    {
        System.IO.File.WriteAllText(@"c:\test\error.txt", error.ToString());
    }
}




Open/closed principle(Without O/cP)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace Solid_Principles
{
    class Program
    {
        static void Main(string[] args)
        {
            Customer customer = new Customer();
            OpenClosedPricnicples openClosedPricnicples = new OpenClosedPricnicples();
            var result = openClosedPricnicples.getDiscount(100, 2);
            Console.WriteLine(result);
            //customer.Add();
            Console.ReadLine();
        }
    }

    class OpenClosedPricnicples
    {
        private int _CustType;

        public int CustType
        {
            get { return _CustType; }
            set { _CustType = value; }
        }

        public double getDiscount(double TotalSales, int _CustType)
        {
            if (_CustType == 1)
            {
                return TotalSales - 100;
            }
            else
            {
                return TotalSales - 50;
            }
        }
    }
}

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


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace Solid_Principles
{
    class Program
    {
        static void Main(string[] args)
        {
            OpenClosedPricnicples openClosedPricnicples = new OpenClosedPricnicples();
            var result = openClosedPricnicples.getDiscount(100);

            SilverCustomer silverCustomer = new SilverCustomer();
            var ResultSilver = silverCustomer.getDiscount(200);

            GoldCustomer goldCustomer = new GoldCustomer();
            var ResultGold = goldCustomer.getDiscount(500);

            Console.WriteLine(result);
            Console.WriteLine(ResultSilver);
            Console.WriteLine(ResultGold);
            Console.ReadLine();
        }
    }


    class OpenClosedPricnicples
    {
        public virtual double getDiscount(double TotalSales)
        {
            return TotalSales;
        }
    }

    class SilverCustomer : OpenClosedPricnicples
    {
        public override double getDiscount(double TotalSales)
        {
            return base.getDiscount(TotalSales) - 50;
        }
    }

    class GoldCustomer : SilverCustomer
    {
        public override double getDiscount(double TotalSales)
        {
            return base.getDiscount(TotalSales) - 100;
        }
    }
}

Putting in simple words the “OpenClosedPricnicples” class is now closed for any new modification but it’s open for extensions when new customer types are added to the project.




Liskov Principles (LSP)

L stands for the Liskov Substitution Principle (LSP) and states that you should be able to use any derived class in place of a parent class and have it behave in the same manner without modification. It ensures that a derived class does not affect the behavior of the parent class, i.e. that a derived class must be substitutable for its base class.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Liskov_Principles
{
    class Program
    {
        static void Main(string[] args)
        {
            Animal animal = new Dog();
            Console.WriteLine(animal.Walk());
            Console.WriteLine(animal.Run());
            Console.WriteLine(animal.Fly());
            Console.WriteLine(animal.MakeNoise());
            Console.WriteLine("\n");
            Animal animal1 = new Bird();
            Console.WriteLine(animal1.Walk());
            Console.WriteLine(animal1.Run());
            Console.WriteLine(animal1.Fly());
            Console.WriteLine(animal1.MakeNoise());

            Console.ReadLine();
        }
    }

    public class Animal
    {
        public string Walk()
        {
            return "Move feet";
        }

        public string Run()
        {
            return "Move feet quickly";
        }

        public virtual string Fly()
        {
            return null;
        }

        public virtual string MakeNoise()
        {
            return null;
        }
    }

    public class Dog : Animal
    {
        public override string MakeNoise()
        {
            return "Bark";
        }
    }

    public class Bird : Animal
    {
        public override string MakeNoise()
        {
            return "Chirp";
        }

        public override string Fly()
        {
            return "Flag wings";
        }
    }
}

Interface Segregation Principle (ISP)
The interface-segregation principle (ISP) states that no client should be forced to depend on methods it does not use. ISP splits interfaces that are very large into smaller and more specific ones so that clients will only have to know about the methods that are of interest to them. (Wiki)
We can define it in another way. An interface should be more closely related to the code that uses it than code that implements it. So the methods on the interface are defined by which methods the client code needs than which methods the class implements. So clients should not be forced to depend upon interfaces that they don't use.
Interface Segregation Principle (Without ISP)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InterfaceSegregationPrinciples_ISP
{
    class Program
    {
        static void Main(string[] args)
        {
            Desktop desktop = new Desktop();
            desktop.Monitor();           
            desktop.WifiConnection();

            Laptop laptop = new Laptop();
            laptop.Monitor();           
            laptop.WifiConnection();
            Console.ReadLine();
        }
    }

    public interface IComputer
    {
        void Monitor();
        void WifiConnection();
    }

    public class Desktop : IComputer
    {
        public void Monitor()
        {
            Console.WriteLine("I am from MonitorTest");
        }

        public void WifiConnection()
        {
            //throw new NotImplementedException("not Implemented");
        }
    }

    public class Laptop : IComputer
    {
        public void Monitor()
        {
            //throw new NotImplementedException();
        }
        public void WifiConnection()
        {
            Console.WriteLine("I am from Wifi Connection of Laptop");
        }
    }
}

Since the Desktop can’t use the WifiConnection, but implementing IComputer interface we have to implement the Wificonnection in  Desktop class. We need to provide a concrete method. Here we are forcing the Desktop Class to implement a WifiConnection method without purpose. This is not a good idea and violating ISP Principle.

Since we have two classes Desktop and Laptop.  Separate the interface for each classes. For Desktop Implemented IComputer Interface, for laptop use ILaptop Interface. We need to divid the responsibilities by segregating the Icomputer, ILaptop interfaces respectively.


Interface Segregation Principle (With ISP)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InterfaceSegregationPrinciples_ISP
{
    class Program
    {
        static void Main(string[] args)
        {
            Desktop desktop = new Desktop();
            desktop.Monitor();
           

            Labtop labtop = new Labtop();           
            labtop.WifiConnection();
            Console.ReadLine();
        }
    }

    public interface IComputer
    {
        void Monitor();
    }

    public interface ILaptop
    {
        void WifiConnection();
    }

    public class Desktop : IComputer
    {
        public void Monitor()
        {
            Console.WriteLine("I am from MonitorTest");
        }
    }

    public class Labtop : ILaptop
    {
        public void WifiConnection()
        {
            Console.WriteLine("I am from Wifi Connection of Laptop");
        }
    }
}


Dependency Inversion Principle (DIP)
(Microsoft)

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.
This principle is primarily concerned with reducing dependencies amongst the code modules. We can think of it as needing the low-level objects to define contracts that the high-level objects can use, without the high-level objects needing to care about the specific implementation the low-level objects provide.
Simple Example
We are sending notifications to the client Like Email or SMS.

Example
Dependency Inversion Principle (Without DIP)

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

namespace SOLIDPrinciplesDemo
{
//DIP Violation
// Low level Class
public class BankAccount
{
    public string AccountNumber { get; set; }
    public decimal Balance { get; set; }
        
    public void AddFunds(decimal value)
    {
        Balance += value;
    }
    public void RemoveFunds(decimal value)
    {
        Balance -= value;
    }
}
// High level Class
public class TransferAmount
{
    public BankAccount Source { get; set; }
    public BankAccount Destination { get; set; }
    public decimal Value { get; set; }

    public void Transfer()
    {
        Source.RemoveFunds(Value);
        Destination.AddFunds(Value);
    }
}
/* 
Problem with above design:

1. The high level TransferAmount class is directly dependent upon the lower level BankAccount class i.e. Tight coupling.
2. The Source and Destination properties reference the BankAccount type.So impossible to substitute other account types unless they are subclasses of BankAccount. 
3. Later we want to add the ability to transfer money from a bank account to pay bills, the BillAccount class would have to inherit from BankAccount. 
 As bills would not support the removal of funds, 
 3.A. This is likely to break the rules of the Liskov Substitution Principle (LSP) or 
 3.B. Require changes to the TransferAmount class that do not comply with the Open/Closed Principle (OCP).

4. Any extension functionality changes be required to low level modules. 
 4.A. Change in the BankAccount class may break the TransferAmount. 
 4.B. In complex scenarios, changes to low level classes can cause problems that cascade upwards through the hierarchy of modules. 
5. As the software grows, this structural problem can be compounded and the software can become fragile or rigid.
6. Without the DIP, only the lowest level classes may be easily reusable.
7. Unit testing should be redone when there is a change in high level or low level classes.
8. Time taken process to change the existing functionality and extending the functionality
*/ 

//Applying DIP resolves these problems by removing direct dependencies between classes.  

Dependency Inversion Principle (With DIP)

using System;

namespace HashTable_and_Dictionary
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Dependency Inversion Principle\n\n");
            Console.WriteLine("Balance in Source Account\n");
            ITransfer_Source transfer_Source = new BankAccount();
            transfer_Source.AccountNumber = 112233;
            transfer_Source.Balance = 5000;
            Console.WriteLine("Account Number : {0}  ", transfer_Source.AccountNumber);
            Console.WriteLine("Account Balance : {0} \n", transfer_Source.Balance);

            Console.WriteLine("Balance in Target Account\n");
            ITransfer_Destination transfer_Destination = new BankAccount();
            transfer_Destination.AccountNumber = 121212;
            transfer_Destination.Balance = 0;
            Console.WriteLine("Account Number : {0}  ", transfer_Destination.AccountNumber);
            Console.WriteLine("Account Balance : {0} \n", transfer_Destination.Balance);

            Console.Write("Enter amount to Transfer : ");
            int transfer_Amount = Convert.ToInt32(Console.ReadLine());
            Console.WriteLine("\n");

            if (transfer_Amount > transfer_Source.Balance)
            {
                Console.WriteLine("Insufficient Amount to Transfer");
                Console.ReadLine();
                return;
            }
            TransferAmount TransferSourceDestination = new TransferAmount();
            TransferSourceDestination.Amount = transfer_Amount;

            TransferSourceDestination.Transfer(transfer_Source, transfer_Destination);

            Console.WriteLine("Source Account Balance : {0} \n ", transfer_Source.Balance);
            Console.WriteLine("Target Account Balance : {0} \n ", transfer_Destination.Balance);
            Console.ReadLine();
        }
    }

    public interface ITransfer_Source
    {
        long AccountNumber { get; set; }
        decimal Balance { get; set; }
        void RemoveAmount(decimal value);
    }

    public interface ITransfer_Destination
    {
        long AccountNumber { get; set; }
        decimal Balance { get; set; }
        void AddAmount(decimal value);
    }

    public class BankAccount : ITransfer_Source, ITransfer_Destination
    {
        public long AccountNumber { get; set; }
        public decimal Balance { get; set; }
        public void RemoveAmount(decimal value)
        {
            Balance -= value;
        }

        public void AddAmount(decimal value)
        {
            Balance += value;
        }
    }

    public class TransferAmount
    {
        public decimal Amount { get; set; }
        public void Transfer(ITransfer_Source transfer_Source, ITransfer_Destination transfer_Destination)
        {
            transfer_Source.RemoveAmount(Amount);
            transfer_Destination.AddAmount(Amount);
        }
    }
}
/*
Advantage in above example after applying DIP:

1. Higher level classes refer to their dependencies using abstractions, such as interfaces or abstract classes i.e. loose coupling. 
2. Lower level classes implement the interfaces, or inherit from the abstract classes.
3. This allows new dependencies can be substituted without any impact. 
4. Lower levels classes will not cascade upwards as long as they do not involve changing the abstraction.
5. Increases the robustness of the software and improves flexibility. 
6. Separation of high level classes from their dependencies raises the possibility of reuse of these larger areas of functionality. 
7. Minimized risk to affect old funtionallity present in Higher level classes.
8. Testing applies only for  newly added low level classes.
9. Though using this principle implies an increased effort and a more complex code, but it is more flexible. 

Note:
In that case the creation of new low level objects inside the high level classes(if necessary) can not be done using the operator new. 
Instead, some of the Creational design patterns can be used, such as Factory Method, Abstract Factory, Prototype.

The Template Design Pattern is an example where the DIP principle is applied.   
  
This principle cannot be applied for every class or every module. 
If we have a class functionality that is more likely to remain unchanged in the future there is not need to apply this principle.




No comments:

Post a Comment

Azure Service Bus Queue , Table - Send, Read

using Microsoft.ServiceBus.Messaging; using System; using System.Collections.Generic; using System.Linq; using System.Text; ...