Blog

SOLID

S.O.L.I.D is an acronym for the first five object-oriented design(OOD) principles. These principles, when combined together, make it easy for a programmer to develop software that is easy to maintain and extend. They also make it easy for developers to avoid code smells, easily refactor code, and are also a part of the agile or adaptive software development. 

SOLID Principles

Some important point about SOLID design principles.

Generally speaking, SOLID state of matter can be brittle. But while talking about code we are not aiming for brittleness of code.

As far as software designing is concerned, SOLID should be used where code can change shape.

SOLID can come to rescue when all the requirements are not upfront while developing code.

SOLID is not a framework.

SOLID principle is not a library. It is not technology bound. It is not a pattern.

Most of the code written today, though they are written in object-oriented way but they are not object oriented and more or less procedural. Writing code in OO language is not a guarantee that the code will be OO.

But we can make our code OO by using solid principles. We can more productive by using SOLID.

The code is more maintainable and understandable. When code suffers from design smell then SOLID is answer.

How SOLID principles Solves read world Application Problems?

Some of the design smell I which I want to discuss here are.

Rigidity – The design is difficult to change. Without proper use of abstraction and interface the design becomes very rigid. For each and every small change in the functionality we have to test whole logic from start.

Fragility – Design is easy to break. As discussed with a small change there are very high chances of the whole design going for a toss.

Immobility – The design is difficult to reuse. Or rather we cannot easily extend the current design.

Viscosity – It is difficult to do the right thing.

Needless Complexity – Over design.

Following are the five SOLID design principles which we should be aware of:

SRP The Single Responsibility Principle: A class should have one, and only one, reason to change, meaning that a class should have only one job.

OCP The Open Closed Principle: You should be able to extend a class’s behavior, without modifying it.

LSP The Liskov Substitution Principle: If any module is using a Base class then the reference to that Base class can be replaced with a Derived class without affecting the functionality of the module.

ISP The Interface Segregation Principle: Make fine grained interfaces that are client specific.

DIP The Dependency Inversion Principle: Depend on abstractions, not on concrete implementations.

Single Responsibility Principle

Single Responsibility Principle or SRP states that every class should have a single responsibility. There should never be more than one reason for a class to change.

Just because you can add everything you want into your class doesn’t mean that you should. Thinking in terms of responsibilities will help you design your application better. Ask yourself whether the logic you are introducing should live in this class or not. Using layers in your application helps a lot. Split big classes in smaller ones, and avoid god classes. Last but not least, write straightforward comments. If you start writing comments such as in this case, but if, except when, or, then you are doing it wrong.

Below is a code violating the SRP. In the sample code, SRP is violated by mixing the OpenGate and CloseGate responsibility with the core vehicle service functionality.

public class ServiceStation 
{ 
    public void OpenGate() 
    { 
        //Open the gate if the time is later than 9 AM 
    }   

    public void DoService(Vehicle vehicle) 
    { 
        //Check if service station is opened and then 
        //complete the vehicle service 
    }  

    public void CloseGate() 
    { 
        //Close the gate if the time has crossed 6PM 
    } 
} 

The refactored code sample is as follows. A new interface is created and the gate related utility methods are moved to a different class called ServiceStationUtility.

public class ServiceStation 
{ 

    IGateUtility _gateUtility;   

    public ServiceStation(IGateUtility gateUtility) 
    { 
        this._gateUtility = gateUtility; 
    } 

    public void OpenForService() 
    { 
        _gateUtility.OpenGate(); 
    }   

    public void DoService() 
    { 
        //Check if service station is opened and then 
        //complete the vehicle service 
    }   

    public void CloseForDay() 
    { 
        _gateUtility.CloseGate(); 
    } 
} 

public class ServiceStationUtility : IGateUtility 
{ 
    public void OpenGate() 
    { 
        //Open the shop if the time is later than 9 AM 
    }   

    public void CloseGate() 
    { 
        //Close the shop if the time has crossed 6PM 
    } 
}   

public interface IGateUtility 
{ 
    void OpenGate(); 
    void CloseGate(); 
} 

Open/Closed Principle

Open/Closed Principle or OCP states that software entities should be open for extension, but closed for modification.

You should make all member variables private by default. Write getters and setters only when you need them. I’ve already covered this point in a previous article, as the ninth rule of the Object Calisthenics is related to this principle. 

Following is a C# source code violating OCP where a new car has to be added then it will require changes in the core function CalculateMileage.

public class MileageCalculator 
{ 

    IEnumerable<Car> _cars; 

    public MileageCalculator(IEnumerable<Car> cars) { this._cars = cars; }   

    public void CalculateMileage() 
    { 
        foreach (var car in _cars) 
        { 
            if (car.Name == "Audi") 
                Console.WriteLine("Mileage of the car {0} is {1}", car.Name, "10M"); 
            else if (car.Name == "Mercedes") 

                Console.WriteLine("Mileage of the car {0} is {1}", car.Name, "20M"); 
        } 
    } 
} 

The OCP violation can be fixed as shown below, using an interface and creating classes for each car there, by reducing making the CalculateMileage method more generic and extensible. 

public class MileageCalculator 
{ 
    IEnumerable<Car> _cars; 

    public MileageCalculator(IEnumerable<Car> cars) { this._cars = cars; }  

    public void CalculateMileage() 
    { 
        CarController controller = new CarController(); 
        foreach (var car in _cars) 
        { 
                Console.WriteLine("Mileage of the car {0} is {1}", car.Name, controller.GetCarMileage(car.Name)); 
        } 
    } 
}   

public class CarController 
{ 
    List<ICar> cars;  
    public CarController() 
    { 
        cars = new List<ICar>(); 
        cars.Add(new Audi()); 
        cars.Add(new Mercedes()); 
    }  

    public string GetCarMileage(string name) 
    { 
        return cars.First(car => car.Name == name).GetMileage(); 
    } 
}   

public interface ICar  
{  
    string Name { get; set; } 
    string GetMileage(); 
}  

public class Audi : ICar 
{ 
    public string Name { get; set; }  

    public string GetMileage() 
    { 
        return "10M"; 
    } 
} 

public class Mercedes : ICar 
{ 
    public string Name { get; set; }  

    public string GetMileage() 
    { 
        return "20M"; 
    } 
} 

Liskov Substitution Principle

Liskov Substitution Principle or LSP states that objects in a program should be replaceable with instances of their subtypes without altering the correctness of the program.

Look at the following C# code sample where the LSP is broken. Simply, an Orange cannot substitute an Apple, which results in printing the color of apple as Orange.

namespace SolidDemo 
{ 
    class Program 
    { 
        static void Main(string[] args) 
        { 
            Apple apple = new Orange(); 
            Console.WriteLine(apple.GetColor()); 
        } 
    }   

    public class Apple 
    { 
        public virtual string GetColor() 
        { 
            return "Red"; 
        } 
    }   

    public class Orange : Apple 
    { 
        public override string GetColor() 
        { 
            return "Orange"; 
        } 
    } 
} 

Now let us re-factor and make it comply with LSP by having a generic base class for both Apple and Orange.

 

namespace SolidDemo 
{ 
    class Program 
    { 
        static void Main(string[] args) 
        { 
            Fruit fruit = new Orange(); 
            Console.WriteLine(fruit.GetColor()); 
            fruit = new Apple(); 
            Console.WriteLine(fruit.GetColor()); 
        } 
    }   

    public abstract class Fruit 
    { 
        public abstract string GetColor(); 
    }   

    public class Apple : Fruit 
    { 
        public override string GetColor() 
        { 
            return "Red"; 
        } 
    }   

    public class Orange : Apple 
    { 
        public override string GetColor() 
        { 
            return "Orange"; 
        } 
    } 
} 

Interface Segregation Principle

Interface Segregation Principle or ISP states that many client-specific interfaces are better than one general-purpose interface. In other words, you should not have to implement methods that you don’t use. Enforcing ISP gives you low coupling and high cohesion.

When talking about coupling, cohesion is often mentioned as well. High cohesion means to keep similar and related things together. The union of cohesion and coupling is orthogonal design. The idea is to keep your components focused and try to minimize the dependencies between them.

Note that this is similar to the Single Responsibility Principle. An interface is a contract that meets a need. It is ok to have a class that implements different interfaces, but be careful, don’t violate SRP.

In the below-mentioned example, ISP is violated where ProcessCreditCard method is not required by InpersonOrder class but is forced to implement.

public interface IOrder 
    { 
        void Purchase(); 
        void ProcessCreditCard(); 
    }   

    public class OnlineOrder : IOrder 
    { 
        public void Purchase() 
        { 
            //Do purchase 
        }  

        public void ProcessCreditCard() 
        { 
            //process through credit card 
        } 
    }   

    public class InpersionOrder : IOrder 
    { 
        public void Purchase() 
        { 
            //Do purchase 
        }   

        public void ProcessCreditCard() 
        { 
            //Not required for inperson purchase 
            throw new NotImplementedException(); 
        } 
    } 

Now let us fix the violation by breaking down the IOrder interface. 

public interface IOrder 
    { 
        void Purchase(); 
    }  

    public interface IOnlineOrder 
    { 
        void ProcessCreditCard(); 
    }   

    public class OnlineOrder : IOrder, IOnlineOrder 
    { 
        public void Purchase() 
        { 
            //Do purchase 
        }  

        public void ProcessCreditCard() 
        { 
            //process through credit card 
        } 
    }  

    public class InpersionOrder : IOrder 
    { 
        public void Purchase() 
        { 
            //Do purchase 
        } 
    } 

Dependency Inversion Principle

Dependency Inversion Principle or DIP has two key points:

  • Abstractions should not depend upon details;
  • Details should depend upon abstractions.

This principle could be rephrased as use the same level of abstraction at a given level. Interfaces should depend on other interfaces. Don’t add concrete classes to method signatures of an interface. However, use interfaces in your class methods.

Note that the Dependency Inversion Principle is not the same as Dependency Injection. Dependency Injection is about how one object knows about another dependent object. In other words, it is about how one object acquires a dependency. On the other hand, DIP is about the level of abstraction. Also, a Dependency Injection Container is a way to auto-wire classes together. That does not mean you do Dependency Injection though. Look at the Service Locator for example.

Also, rather than working with classes that are tightly coupled, use interfaces. This is called programming to the interface. This reduces dependency on implementation specifics and makes the code more reusable. It also ensures that you can replace the implementation without violating the expectations of that interface, according to the Liskov Substitution Principle seen before.

The following code shows how to do this.

public interface ILogger 
{ 
    void WriteLog(string message); 
}  

public class DatabaseLogger : ILogger 
{ 
    public void WriteLog(string message) 
    { 
        // Format message 

        // Build parameter 

        // Open connection 

        // Send command 

        // Close connection 
    } 
} 

public class TextFileLogger : ILogger 
{ 
    public void WriteLog(string message) 
    { 
        // Format message 

        // Open text file 

        // Write message 

        // Close text file 
    } 
} 

public class Main 
{ 
    public Main() 
    { 
        var processor = new Processor(new TextFileLogger()); 
    } 
}  

public class Processor 
{ 
    private readonly ILogger _logger; 

    public Processor(ILogger logger) 
    { 
        _logger = logger; 
    } 

    public void RunProcessing() 
    { 
        try 
        { 
            // Do the processing 
        } 

        catch (Exception ex) 
        { 
            _logger.WriteLog(ex.Message); 
        } 
    } 
} 

Conclusion

We have gone through all the five SOLID principles successfully with a simple C# example.

SOLID principles of object-oriented programming allow us to write structured and neat code that is easy to extend and maintain