SOLID Principles
There are five
S.O.L.I.D.
principles:
Single Responsibility Principle (SRP)
Open Closed Principle (OCP)
Liskov Substitution Principle (LSP)
Interface Segregation Principle (ISP)
Dependency Inversion Principle (DIP)
Advantage of SOLID Principles
Single Responsibility Principle (SRP)
A class should have only one reason to change.
Robert C. Martin gave this definition in his book Agile Software Development, Principles, Patterns and Practices and later republished in the C# version of the book Agile Principles, Patterns and Practices in C#. In layman terminology, this means that a class should not be loaded with multiple responsibilities and a single responsibility should not be spread across multiple classes or mixed with other responsibilities. The reason is that more changes requested in the future, the more changes the class need to apply.
Single Responsibility Principle (SRP) is one of the five SOLID principles which guide developers as they write code or design an application.
In simple terms, a module or class should have a very small piece of responsibility in the entire application. Or as it states, a class/module should have not more than one reason to change.
If a class has only a single responsibility, it is likely to be very robust. It’s easy to verify its working as per logic defined. And it’s easy to change in class as it has single responsibility. The Single Responsibility Principle provides another benefit. Classes, software components and modules that have only one responsibility are much easier to explain, implement and understand than ones that give a solution for everything. This also reduces number of bugs and improves development speed and most importantly makes developer’s life lot easier.
Let’s take a scenario of Garage service station functionality. It has 3 main functions; open gate, close gate and performing service. Below example violates SRP principle. The code below, violates SRP principle as it mixes open gate and close gate responsibilities with the main function of servicing of vehicle.public class GarageStation { public void DoOpenGate() { //Open the gate functinality } public void PerformService(Vehicle vehicle) { //Check if garage is opened //finish the vehicle service } public void DoCloseGate() { //Close the gate functinality } }
public class GarageStation { IGarageUtility _garageUtil; public GarageStation(IGarageUtility garageUtil) { this._garageUtil = garageUtil; } public void OpenForService() { _garageUtil.OpenGate(); } public void DoService() { //Check if service station is opened and then //finish the vehicle service } public void CloseGarage() { _garageUtil.CloseGate(); } } public class GarageStationUtility : IGarageUtility { public void OpenGate() { //Open the Garage for service } public void CloseGate() { //Close the Garage functionlity } } public interface IGarageUtility { void OpenGate(); void CloseGate(); }
Open Closed Principle (OCP)
Software entities such as classes, modules, functions, etc. should be open for extension, but closed for modification
Above statement means, any new functionality should be implemented by adding new classes, attributes and methods, instead of changing the current ones or existing ones.
The easiest way to apply OCP is to implement the new functionality on new derived (sub) classes that inherit the original class implementation.
Another technique is to allow client to access the original class with an abstract interface.
So, at any given point of time when there is a requirement change instead of making change in the existing functionality it’s always suggested to create new classes and leave the original implementation untouched.
Liskov Substitution Principle (LSP)
The Liskov Substitution Principle (LSP) states that "you should be able to use any derived class instead 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, in other words,
that a derived class must be substitutable for its base class.
This principle is just an extension of the Open Close Principle.
So, according to LSP we must ensure that new derived classes extend the base classes without changing their behavior.
We should make sure that Clients should not know which specific subtype they are calling, nor should they need to know that. The client should behave the same regardless of the subtype instance that it is given. New derived classes just extend without replacing the functionality of old classes.
Interface segregation Principle (ISP)
The interface-segregation principle (ISP) states that no client should be forced to depend on methods it does not use.
The Interface Segregation Principle (ISP) states that clients should not be forced to depend upon interfaces that they do not use. When we have non-cohesive interfaces, the ISP guides us to create multiple, smaller, cohesive interfaces. The original class implements each such interface. Client code can then refer to the class using the smaller interface without knowing that other members exist.
When you apply the ISP, class and their dependencies communicate using tightly-focussed interfaces, minimising dependencies on unused members and reducing coupling accordingly. Smaller interfaces are easier to implement, improving flexibility and the possibility of reuse. As fewer classes share interfaces, the number of changes that are required in response to an interface modification is lowered. This increases robustness.
Let's see below example to understand ISP
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 InpersonOrder : IOrder { public void Purchase() { //Do purchase } public void ProcessCreditCard() { //Not required for inperson purchase throw new NotImplementedException(); } }
In the above example ISP is violated where ProcessCreditCard method is not required by InpersonOrder class but is forced to implement.
Let us fix the violation.
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 InpersonOrder : IOrder { public void Purchase() { //Do purchase } }
Dependency Inversion Principle (DIP)
Dependency Inversion Principle, provides us the technique to create loosely coupled concrete classes instead of high level classes.
High-level modules/classes Should not depend upon low-level modules/classes. Both should depend upon abstractions.
Secondly, abstractions should not depend upon details. Details should depend upon abstractions.
To understand lets take an example of two classes one is ErrorLogWritter another is ErrorLogManager as shown below.
public class ErrorLogWritter { public void WriteLog(string error) { System.IO.File.WriteAllText("c:\ErrorLog.log", error); } } public class ErrorLogManager { ErrorLogWritter writer = null; public void Notify(string msg) { if(!string.IsNullOrEmpty(msg)) { writer = new ErrorLogWritter(); writer.WriteLog(msg); } } }
In above code ErrorLogWritter class responsibility to write error into the ErrorLog.log file and ErrorLogManager have responsibility to log errors if any error raised with in the entire application. ErrorLogManager is high level class, which directly depends upon low level ErrorLogWritter concrete class. Here code will work fine. This design fails the dependency inversion principle. It will create tightly coupling between classes. With such classes, complex factor increases and understanding the code also increases.
Moreover if client demands to send email for error log and sms error log intimation service etc. Again more complexity increases with two more new classes one for email and other for sms error log service as shown code below.
using System; namespace SOLID { public class ErrorLogWritter { public void WriteLog(string error) { System.IO.File.WriteAllText("c:\ErrorLog.log", error); } } public class SMSService { public void WriteLog(string error) { // } } public class EmailService { public void WriteLog(string error) { // } } public class ErrorLogManager { ErrorLogWritter writer = null; EmailService email = null; SMSService sms = null; public void Notify(string msg) { if(!string.IsNullOrEmpty(msg)) { writer = new ErrorLogWritter(); email = new EmailService(); sms = new SMSService(); writer.WriteLog(msg); email.WriteLog(msg); sms.WriteLog(msg); } } } }