SOLID – Application Development Principles

SOLID Principles – PDF Document Well Formatted.

Source Code Can be downloaded from here

SOLID are five basic principles whichhelp to create good software architecture. SOLID is an acronym where:-

  • S stands for SRP (Single responsibility principle
  • O stands for OCP (Open closed principle)
  • L stands for LSP (Liskov substitution principle)
  • I stands for ISP ( Interface segregation principle)
  • D stands for DIP ( Dependency inversion principle)

Single Responsibility

Single Responsibility Principle states that objects should have single responsibility and all of their behaviors should focus on that one responsibility

    class Customer
    {
        public void Add()
        {
            try
            {
                // Database code goes here
            }
            catch (Exception ex)
            {
                System.IO.File.WriteAllText(@"c:\Error.txt", ex.ToString());
            }
        }
 
    }

The above customer class is doing things WHICH HE IS NOT SUPPOSED TO DO. Customer class should do customer data validations, call the customer data access layer etc , but if you see the catch block closely it also doing LOGGING activity. In simple words its over loaded with lot of responsibility.

 

With Single Responsibility Principle – Move that logging activity to some other class who will only look after logging activities.

class FileLogger
{
    public void Handle(string error)
    {
        System.IO.File.WriteAllText(@"c:\Error.txt", error);
    }
}
 
class Customer
{
    private FileLogger obj = new FileLogger();
    public virtual void Add()
    {
        try
        {
            // Database code goes here
        }
        catch (Exception ex)
        {
            obj.Handle(ex.ToString());
        }
    }
}

 

Open/Closed Principle

Open and Closed principle encourages components that are open for extension, but closed for modification.

class Customer
{
    private FileLogger obj = new FileLogger();
    public virtual void Add()
    {
        try
        {
            // Database code goes here
        }
        catch (Exception ex)
        {
            obj.Handle(ex.ToString());
        }
    }
}
 
class Customer
{
    private int _CustType;
    public int CustType
    {
        get { return _CustType; }
        set { _CustType = value; }
    }
 
    public double getDiscount(double TotalSales)
    {
 
        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. 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. In other words we are “MODIFYING” the current customer code for every change and every time we modify we need to ensure that all the previous functionalities and connected client are working as before. How about rather than “MODIFYING” we go for “EXTENSION”. In other words every time a new customer type needs to be added we create a new class as shown in the below. So whatever is the current code they are untouched and we just need to test and check the new classes.

class Customer
{
    private FileLogger obj = new FileLogger();
    public virtual void Add()
    {
        try
        {
            // Database code goes here
        }
        catch (Exception ex)
        {
            obj.Handle(ex.ToString());
        }
    }
 
    public virtual double getDiscount(double TotalSales)
    {
        return TotalSales;
    }
}
 
class VIPCustomer : Customer
{
    public override double getDiscount(double TotalSales)
    {
        return base.getDiscount(TotalSales) - 50;
    }
}

 

Liskov Substitution Principle

This principle states that object should be easily replaceable by the instances of their sub types without influencing the behavior and rules of the objects. Let’s continue with the same customer. Let’s say our system wants to calculate discounts for Enquiries. Now Enquiries are not actual customer’s they are just leads. Because they are just leads we do not want to save them to database for now. So we create a new class called as Enquiry which inherits from the “Customer” class. We provide some discounts to the enquiry so that they can be converted to actual customers and we override the “Add’ method with an exception so that no one can add an Enquiry to the database.

 

class Enquiry : Customer
{
    public override double getDiscount(double TotalSales)
    {
 
        return base.getDiscount(TotalSales) - 5;
    }
 
 
    public override void Add()
    {
        throw new Exception("Not allowed");
    }
}

So as per polymorphism rule my parent “Customer” class object can point to any of it child class objects i.e. “VIP”, or “Enquiry” during runtime without any issues.

So for instance in the below code you can see I have created a list collection of “Customer” and thanks to polymorphism I can add “VIP” and “Enquiry” customer to the “Customer” collection without any issues.

List<Customer> customers = new List<Customer>();
customers.Add(new VIPCustomer());
customers.Add(new Enquiry());
foreach (Customer customer in customers)
{
    customer.Add();
}

As per the inheritance hierarchy the “Customer” object can point to any one of its child objects and we do not expect any unusual behavior. But when “Add” method of the “Enquiry” object is invoked it leads to below error because our “Equiry” object does save enquiries to database as they are not actual customers. So LISKOV principle says the parent should easily replace the child object. So to implement LISKOV we need to create two interfaces one is for discount and other for database as shown below.

interface IDiscount
{
    double getDiscount(double TotalSales);
}
 
interface IDatabase
{
    void Add();
}

Now the “Enquiry” class will only implement “IDiscount” as he not interested in the “Add” method.

class Enquiry : IDiscount
{
    public double getDiscount(double TotalSales)
    {
        return TotalSales - 5;
    }
}

While the “Customer” class will implement both “IDiscount” as well as “IDatabase” as it also wants to persist the customer to the database.

class Customer : IDiscountIDatabase
{
    private FileLogger obj = new FileLogger();
    public virtual void Add()
    {
        try
        {
            // Database code goes here
        }
        catch (Exception ex)
        {
            obj.Handle(ex.ToString());
        }
    }
 
    public virtual double getDiscount(double TotalSales)
    {
        return TotalSales;
    }
}

Now there is no confusion, we can create a list of “IDatabase” interface and add the relevant classes to it. In case we make a mistake of adding “Enquiry” class to the list compiler would complain.

Interface Segregation Principle

It encourages the use of Interface but limits the size of the interface. Instead of one super class interface that contains all the behavior for an object, there should exist multiple smaller more specific interfaces

e.g. .NET has separate interface for serialization and disposing (ISerializaable and IDisposable)

Now assume that our customer class has become a SUPER HIT component and it’s consumed across 1000 clients and they are very happy using the customer class. Now let’s say some new clients come up with a demand saying that we also want a method which will help us to “Read” customer data. So developers who are highly enthusiastic would like to change the “IDatabase” interface as shown below.

But by doing so we have done something terrible, can you guess?

interface IDatabase
{
    void Add(); // old client are happy with these.
    void Read(); // Added for new clients.
}

 

If you visualize the new requirement which has come up, you have two kinds of client’s: –

Who want’s just use “Add” method. The other who wants to use “Add” + “Read”. Now by changing the current interface you are doing an awful thing, disturbing the 1000 satisfied current client’s , even when they are not interested in the “Read” method. You are forcing them to use the “Read” method. So a better approach would be to keep existing clients in their own sweet world and the serve the new client’s separately. So the better solution would be to create a new interface rather than updating the current interface. So we can keep the current interface “IDatabase” as it is and add a new interface “IDatabaseV1” with the “Read” method the “V1” stands for version 1.

interface IDatabaseV1 : IDatabase // Gets the Add method
{
    void Read();
}

You can now create fresh classes which implement “Read” method and satisfy demands of your new clients and your old clients stay untouched and happy with the old interface which does not have “Read” method.

 

class CustomerWithRead : IDatabaseIDatabaseV1
{
    public void Add()
    {
        Customer obj = new Customer();
        obj.Add();
    }
    public void Read()
    {
        // Implements logic for read
    }
}

So the old clients will continue using the “IDatabase” interface while new client can use “IDatabaseV1” interface.

IDatabase i = new Customer(); // 1000 happy old clients not touched
i.Add();
IDatabaseV1 iv1 = new CustomerWithRead(); // new clients
iv1.Read();

Dependency Inversion Principle

Components that depend on each other should interact via an abstraction and not directly with concrete implementation. Inversion of Controls Glues all these principles together. Two proper implementation for IoC are

  • Dependency Injection
  • Service Location

 

Major difference between two implementations revolves around how the dependencies are accessed

Service Locator relies on the caller to invoke and ask for dependency

Dependency injection relies on injecting the dependency in to the class through constructor, setting one of its properties or executing one of its methods.

 

In our customer class if you remember we had created a logger class to satisfy SRP. Down the line let’s say new Logger flavor classes are created.

 

class Customer : IDiscountIDatabase
{
    private FileLogger obj = new FileLogger();
    public virtual void Add()
    {
        try
        {
            // Database code goes here
        }
        catch (Exception ex)
        {
            obj.Handle(ex.ToString());
        }
    }
    public virtual double getDiscount(double TotalSales)
    {
        return TotalSales;
    }
}

Just to control things we create a common interface and using this common interface new logger flavors will be created.

interface ILogger
{
    void Handle(string error);
 
}

Below are three logger flavors and more can be added down the line.

class FileLogger : ILogger
{
    public void Handle(string error)
    {
        System.IO.File.WriteAllText(@"c:\Error.txt", error);
    }
 
}
class EverViewerLogger : ILogger
{
    public void Handle(string error)
    {
        // log errors to event viewer
    }
}
class EmailLogger : ILogger
{
    public void Handle(string error)
    {
        // send errors in email
    }
 
}

Now depending on configuration settings different logger classes will used at given moment. So to achieve the same we have kept a simple IF condition which decides which logger class to be used, see the below code.

class Customer : IDiscountIDatabase
{
    private ILogger obj;
    public virtual void Add(int exhandle)
    {
        try
        {
            // Database code goes here
        }
        catch (Exception ex)
        {
            if (exhandle == 1)
            {
                obj = new FileLogger();
            }
            else
            {
                obj = new EmailLogger();
            }
            obj.Handle(ex.Message.ToString());
        }
    }
 
    public virtual double getDiscount(double TotalSales)
    {
        return TotalSales;
    }
}

The above code is again violating SRP but this time the aspect is different, it’s about deciding which objects should be created. Now it’s not the work of “Customer” object to decide which instances to be created, he should be concentrating only on Customer class related functionalities. If you watch closely the biggest problem is the “NEW” keyword. He is taking extra responsibilities of which object needs to be created. So if we INVERT / DELEGATE this responsibility to someone else rather the customer class doing it that would really solve the problem to a certain extent. So here’s the modified code with INVERSION implemented. We have opened the constructor mouth and we expect someone else to pass the object rather than the customer class doing it. So now it’s the responsibility of the client who is consuming the customer object to decide which Logger class to inject.

class Customer : IDiscountIDatabase
{
    private ILogger logger;
    public Customer(ILogger logger)
    {
        this.logger = logger;
    }
 
    public virtual void Add()
    {
        try
        {
            // Database code goes here
        }
        catch (Exception ex)
        {
            logger.Handle(ex.ToString());
        }
    }
    public virtual double getDiscount(double TotalSales)
    {
        return TotalSales;
    }
}

So now the client will inject the Logger object and the customer object is now free from those IF condition which decide which logger class to inject. This is the Last principle in SOLID Dependency Inversion principle. Customer class has delegated the dependent object creation to client consuming it thus making the customer class concentrate on his work.

IDatabase i = new Customer(new EmailLogger());
Advertisements

One thought on “SOLID – Application Development 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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s