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
Identify Problem in Programming
Problems
- Every time insert logic changes, this class will change.
- Every time report format changes, this class will changes.
- A single change leads to double testing (or maybe more).
Solutions which will not Violate SRP
We can create three different classes
public class Employee
- Employee – Contains Properties (Data)
- EmployeeDB – Does database operations
- 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
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);
}
}
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.
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. As you can see the client depends on ILogger which can be set to an instance of any derived class.
Dependency Injection
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);
}
}