SOLID PRINCIPLE
Single Responsibility Principle
Fully Loaded
|
Single Responsibility Principle
|
class Customer
{
public void Add()
{
try
{ // Database code goes here }
catch (Exception
ex)
{ System.IO.File.WriteAllText(@"c:\Error.txt",
ex.ToString());
}
}
}
|
class Customer
{
private FileLogger obj
= new FileLogger();
publicvirtual void
Add()
{
try
{
// Database
code goes here
}
catch (Exception
ex) {
obj.Handle(ex.ToString());
}
}
}
class FileLogger
{
public void
Handle(string error)
{
System.IO.File.WriteAllText(@"c:\Error.txt", error);
}
}
|
The above customer class
is doing things WHICH HE IS NOT SUPPOSED TO DO. Customer class should do
customer datavalidations, 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.
|
SingleResposibilityPrinciple says that a class should have only one
responsibility and not multiple.So if we apply SRP we can move that logging
activity to some other class who will only look after logging activities.
Hide Copy Code
Now customer class can happily delegate the logging activity to the
“FileLogger” class and he can concentrate on customer related activities.
|
Open Close Principle
Tightly coupled functions
|
Open Close Principle
|
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;
}
}
}
|
class Customer
{
public virtual double
getDiscount(double TotalSales)
{
return TotalSales;
}
}
class SilverCustomer :
Customer
{
public override double
getDiscount(double TotalSales)
{
return
base.getDiscount(TotalSales) - 50;
}
}
class goldCustomer : SilverCustomer
{
public override double
getDiscount(double TotalSales)
{
return
base.getDiscount(TotalSales) - 100;
}
}
|
I have added a simple customer type property
to the class. This property decided if this is a “Gold” ora “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.
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.
|
Instead of modifying the existing functionality. 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.
|
Liskov substitution Principle
Code Block
|
Liskov substitution Principle
|
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;
}
}
}
class Enquiry : Customer
{
public override double
getDiscount(double TotalSales)
{
return
base.getDiscount(TotalSales) - 5;
}
public override void
Add()
{
throw new
Exception("Not allowed");
}
}
Class program
{
Public static void main()
{
List<Customer> Customers = new List<Customer>();
Customers.Add(new SilverCustomer());
Customers.Add(new goldCustomer());
Customers.Add(new Enquiry());
foreach (Customer o in
Customers)
{
o.Add();
}}}
|
interface IDiscount
{
double
getDiscount(double TotalSales);
}
interface IDatabase
{
void Add();
}
class Enquiry : IDiscount
{
public double getDiscount(double TotalSales)
{
return TotalSales -
5;
}
}
class Customer : IDiscount, IDatabase
{
private MyException obj
= new MyException();
public virtual void
Add()
{
try
{
// Database code
goes here
}
catch (Exception ex)
{
obj.Handle(ex.Message.ToString());
}
}
public virtual double
getDiscount(double TotalSales)
{
return TotalSales;
}
}
|
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.
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.
|
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.
Now the “Enquiry” class will only implement “IDiscount” as he not
interested in the “Add” method.
While the “Customer” class will implement both “IDiscount” as well as
“IDatabase” as it also wants to persist the customer to the database.
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 as shown in the
below code snippet.
|
ISP (Interface Segregation principle)
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.
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.
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 : IDatabase, IDatabaseV1
{
public void Add()
{
Customer obj = new Customer();
Obj.Add();
}
Public void 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
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
{
private FileLogger obj = new FileLogger();
public virtual void Add()
{
try
{
// Database code goes here
}
catch (Exception ex)
{
obj.Handle(ex.ToString());
}
}
}
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)
{ .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 : IDiscount, IDatabase
{
private IException obj;
public virtual void Add(int Exhandle)
{
try
{
// Database code goes here
}
catch (Exception ex)
{
if (Exhandle == 1)
{
obj = new MyException();
}
else
{
obj = new EmailException();
}
obj.Handle(ex.Message.ToString());
}
}
The above code is again violating SRP but this time the aspect is different ,its 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 : IDiscount, IDatabase
{
private Ilogger obj;
public Customer(ILogger i)
{
obj = i;
}
}
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());
Comments
Post a Comment