Friday, 10 April 2015

What is SOLID?

SOLID are five basic principles which help 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)

“S”- Single responsibility principle

Meaning: An object should only have one reason to change; the longer the file or class; the more difficult it will be to achieve this. 

Have a look at the code below, can you guess what the problem is?

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. So tomorrow if add a new logger like event viewer I need to go and change the “Customer”class, that’s very ODD.

“O” - Open closed principle

Meaning: Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
Let’s continue with our same customer class example. I have added a simple customer type property to the class. This property decided if this is a “Gold” or “Silver” customer.
Depending on the same it calculates discount. Have a look at the “getDiscount” function which returns discount accordingly. 1 for Gold customer and 2 for Silver customer.

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.
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.

“L”- Liskov substitution principle

Meaning: If S is a subtype of T, then objects of type T may be replaced with objects of type S (in other words, objects of type S may substitute objects of type T) without altering any of the desirable properties of that program (correctness, task performed, and so on).

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 int getArea()
{
return _width * _height;
}

public class Square : Rectangle  // In an "is a" relationship, the derived class is clearly a
//kind of the base class
{
public override void SetWidth(int width)
{
_width = width;
_height = width;
}

public override void SetHeight(int height)
{
_height = height;
_width = height;
}
}

public void AreaOfRectangle()
{
Rectangle r = RectangleFactory(); // Returns the rectangle type object
r.SetWidth(7);
r.SetHeight(3);
r.getArea();
}

So can you tell me the out put of the  r.getArea(); method . Yes very simple the expected output is

public  Rectangle RectangleFactory()
{
return new Square();
}

One thing i want to mention about the  RectangleFactory() is that , this method is now exposed to you. But think as if you are getting the Rectangle object just by using a factory dll or from any service where you have no idea what type of rectangle object will be return.

So what’s wrong there? Remember as I said earlier
Now the solution is to manage the class inheritance hierarchies correctly. Let’s introduce another class

public class Quadrilaterals
{
public virtual int Height { get; set; }
public virtual int Width { get; set; }
public int getArea()
{
return Height * Width;
}
}
public class Rectangle :Quadrilaterals
{

public override int Width
{
get { return base.Width; }
set { base.Width = value; }
}
public override int Height
{
get { return base.Height; }
set { base.Height = value; }
}  

}

public class Square : Quadrilaterals  // In an "is a" relationship, the derived class is clearly a
//kind of the base class
{
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;
}
}

“I” - Interface Segregation principle

Meaning: The principle states that no client should be forced to depend on methods that it does not use
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” interfaceas shown below.

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

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 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();
}

“D”- Dependency inversion principle

Meaning:
  • 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.
About the author:
Akash Singh Verma is a consultant in Systems Plus Pvt. Ltd. Within Systems Plus, he actively contributes to the areas of Technology and Information Security. He can be contacted at akash.v@spluspl.com

5 comments: