SOLID architecture
principles help to create good software architecture; basically, it has five principles known as SOLID. SOLID is a short form
you can see below the full form of SOLID:
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)
Below I have
started to describe each principle with c# examples
“S”- SRP (Single responsibility principle)
It would be
better if first we understand the problem and try to resolve it using the SOLID architecture principle.
See below
code and think about what may be the exact problem here.
public class Orders
{
public void SaveOrders()
{
try
{
//
Do your database code to save order
}
catch (Exception ex)
{
System.IO.File.WriteAllText(@"D:\Elog.txt",
ex.ToString());
}
}
}
The above Order class should do customer data validations, call the Order data access layer, etc., but if you notice the catch block, it is maintaining log activity. It means
Order class overloaded with a lot of responsibility.
If in catch the blog we need to add a new logger like event viewer I need to go and change the
“Order” class, that’s not a good practice.
If we make a separate
class for the logging “System.IO.File.WriteAllText(@"D:\Elog.txt",
ex.ToString());” it would be better for future changes in the log, It will
not disturb to Order class.
SRP says Class should be responsible for only one activity.
class Log
{
public void ErrorHandle(string ex)
{
File.WriteAllText(@"D:\Elog.txt", ex.ToString());
}
}
Now Order Class can easily use the logging activity to the “Log” class, and he can give
attention to Order related activities.
public abstract class Order
{
Log oLog = new Log();
public virtual void SaveOrder(decimal price)
{
try
{
//Save
Order Goes Here
}
catch(Exception ex)
{
oLog.ErrorHandle(ex.ToString());
}
}
}
“O”
- Open closed principle
I will continue with Order Example I have added a simple code regarding discount voucher, add voucher property to order class. This property decided user has “GoldCoupon”
or “SilverCoupon” regarding this property we have to decide we give discount or
not.
class Order
{
private string _voucherType;
public string VoucherType
{
get { return _voucherType; }
set { _voucherType = value; }
}
public double Discount(decimal totalPrice)
{
if (VoucherType == “GoldCoupon”)
{
return totalPrice - (totalPrice*10)/100;
}
else
{
return totalPrice - (totalPrice*5)/100;
}
}
public double SaveOrder(decimal totalPrice)
{
//Database
Code here
}
}
The problem
is if we need to add a new Voucher type we need to go and add one more “IF”
condition in the “Discount” function, in other words, we need to change the Order
class.
If we are
changing the Order class, again and again, we need to ensure that the previous
conditions with new ones are working fine or not and test again, also need to
ensure existing user’s or client’s which are referencing this class are working
properly as before.
Now we can
see the modification of current “Order” class code for every change and every time
and also ensure that all previous functionalities are working as before.
Question is
arise here,
How can we
go for extension except for modification of Order class?
The answer is we can add a new class for a voucher type; see below like this current code will
untouched and in the test, we just analyze the new class only.
public abstract class Order
{
public virtual decimal Discount(decimal totalPrice)
{
return totalPrice;
}
public virtual decimal VoucherEnquiry(decimal totalPrice, string VoucherType)
{
return totalPrice;
}
public abstract void SaveOrder(decimal price);
}
class GoldCoupon : Order
{
public override decimal Discount(decimal totalPrice)
{
return base.Discount((totalPrice * 10) / 100);
}
public override void SaveOrder(decimal price)
{
decimal totalPrice = Discount(price);
//Database
Code here
}
}
class SilverCoupon : Order
{
public override decimal Discount(decimal totalPrice)
{
return base.Discount((totalPrice * 5) / 100);
}
public override void SaveOrder(decimal price)
{
decimal totalPrice = Discount(price);
//Database
Code here
}
}
Now one new
Voucher type “BronzeCoupon ” needs to add here; without any code change in existing classes, we can add easily. See below.
class BronzeCoupon : Order
{
public override decimal Discount(decimal totalPrice)
{
return base.Discount((totalPrice * 2) / 100);
}
public override void SaveOrder(decimal price)
{
decimal totalPrice = Discount(price);
//Database
Code here
}
}
L -
LSP (Liskov substitution principle)
I continue with the same example, Suppose discount Enquiries need to add in our system. If any
user demanded discount inquiry so we will not save it to our database we
just give him info about the discount.
Now we will
create a new class named “VchrEnquiry” which inherits from the “Order”
class.
See bellow
class VchrEnquiry : Order
{
public override decimal VoucherEnquiry(decimal totalPrice, string VoucherType)
{
//code
here for a discount inquiry like GoldCoupon, SilverCoupon, etc.
return base.Discount((totalPrice * 2) / 100);
}
public override void SaveOrder(decimal price)
{
throw new Exception("This is not
allowed");
}
}
Above code
is for new class VchrEnquiry one the problem we are facing here, we don’t need to save order in the database but as per
abstract method rule, we have to implement SaveOrder a method so we will code like the same.
So as per
polymorphism rule my parent “Order” class object can point to any of it child
class objects i.e. “GoldCoupon”, “SilverCoupon” or “VchrEnquiry” during runtime
without any issues.
So for instance in the below code you can see I have created a list collection of
“Customer” and thanks to polymorphism I can add “GoldCoupon”, “SilverCoupon”
and “VchrEnquiry” user to the “Order” collection without any issues.
List<Order> Orders = new List<Order>();
Orders.Add(new GoldCoupon());
Orders.Add(new SilverCoupon());
Orders.Add(new VchrEnquiry());
Orders.Add(new BronzeCoupon());
foreach (Order o in Orders)
{
o.SaveOrder(10);
}
When you
will run the code you will find an error at SaveOrder method in VchrEnquiry
class.
In other
words the VchrEnquiry has discount calculation;
It looks like an Order class, but it is not an Order. So the parent cannot replace the child object seamlessly. In other words, the Order class is not the
actual parent for the VchrEnquiry
class. VchrEnquiry is a different
entity altogether. So the LISKOV principle
says the parent should easily replace the child object, so we need to
create two interfaces one is for discount and other for save order into
the database, see below.
public interface ISaveOrder
{
void SaveOrder(decimal price);
}
public interface IDiscount
{
decimal Discount(decimal totalPrice);
}
Now the VchrEnquiry class will only implement IDiscount.
class VchrEnquiry : IDiscount
{
public override decimal VoucherEnquiry(decimal totalPrice)
{
//your
discount code here
return 10;
}
}
Order class will implement both IDiscount as well as ISaveOrder as it also saves the order to
the database.
public class Order:IDiscount,ISaveOrder
{
public virtual decimal Discount(decimal totalPrice)
{
return totalPrice;
}
Log oLog = new Log();
public virtual void SaveOrder(decimal price)
{
try
{
//Save
Order Goes Here
}
catch(Exception ex)
{
oLog.ErrorHandle(ex.ToString());
}
}
}
And other the class will be the same as below
class GoldCoupon : Order
{
public override decimal Discount(decimal totalPrice)
{
return base.Discount((totalPrice * 10) / 100);
}
public override void SaveOrder(decimal price)
{
decimal totalPrice = Discount(price);
//Database
Code here
}
}
class SilverCoupon : Order
{
public override decimal Discount(decimal totalPrice)
{
return base.Discount((totalPrice * 5) / 100);
}
public override void SaveOrder(decimal price)
{
decimal totalPrice = Discount(price);
//Database
Code here
}
}
class BronzeCoupon : Order
{
public override decimal Discount(decimal totalPrice)
{
return base.Discount((totalPrice * 2) / 100);
}
public override void SaveOrder(decimal price)
{
decimal totalPrice = Discount(price);
//Database
Code here
}
}
Now without the confusion, we can create a list of SaveOrder
.
List<Order> Orders = new List<Order>();
Orders.Add(new GoldCoupon());
Orders.Add(new SilverCoupon());
//Orders.Add(new
VchrEnquiry()); this line will show error
Orders.Add(new BronzeCoupon());
foreach (Order o in Orders)
{
o.SaveOrder(10);
}
I - ISP (Interface Segregation principle)
Now your application
going well and you have more clients, some new client’s demanded view of
Orders after Order Make.
Now what
will you do, if you decided to change existing code may be existing client
don’t want this facility, now what you will do, see below
public interface IOrderViewV1
{
void ViewOrder();
}
Add a
interface with name IOrderViewV1 implement it in new class OrderView see
below
public class OrderView : ISaveOrder,IOrderViewV1
{
Log oLog = new Log();
public virtual void SaveOrder(decimal price)
{
try
{
//Save
Order Goes Here
}
catch (Exception ex)
{
oLog.ErrorHandle(ex.ToString());
}
}
public void ViewOrder()
{
//your
Order view logic will go here
}
}
So
here old client will use ISaveOrder Interface, and new will use IOrderView Interface.
ISaveOrder
oI = new Order();
oI.SaveOrder(10);
IOrderViewV1 oIV1 = new OrderView();
oIV1.ViewOrder();
D- Dependency inversion principle
Guys this a very important topic for the interview and ask me many times to me here I tried
to explain about Dependency inversion principle.
See below code, in SRP we just discussed this code make a separate class for the
error log.
public class Order
{
Log oLog = new Log();
public virtual void SaveOrder(decimal price)
{
try
{
//Save
Order Goes Here
}
catch (Exception ex)
{
oLog.ErrorHandle(ex.ToString());
}
}
}
class Log
{
public void ErrorHandle(string ex)
{
File.WriteAllText(@"D:\Elog.txt", ex.ToString());
}
}
Now we have the requirement to error should be sent on user mail or SMS also and it will be
choose by the user if he wants to mail or SMS now what we will do
We implement
an interface for log and put code in catch blog, see below.
class EmailLog:ILog
{
public void ErrorHandle(string ex)
{
//Email
code here
}
}
class smsLog:ILog
{
public void ErrorHandle(string ex)
{
//Sms
code here
}
}
class FileLog : ILog
{
public void ErrorHandle(string ex)
{
//Email
code here
}
}
public class Order
{
private ILog oLog;
public virtual void SaveOrder(decimal price,string exType)
{
try
{
//Save
Order Goes Here
}
catch (Exception ex)
{
if (exType == "File")
{
oLog = new FileLog();
oLog.ErrorHandle(ex.ToString());
}
if (exType == "Email")
{
oLog = new EmailLog();
oLog.ErrorHandle(ex.ToString());
}
if (exType == "Sms")
{
oLog = new smsLog();
oLog.ErrorHandle(ex.ToString());
}
}
}
}
The See
catch block here you find SRP violate, but the aspect is different, it’s about
deciding which object should be instantiated.
Now it’s not
the work of Order object to decide
which instances to be created, he should be concentrating only on Order class-related functionalities.
The biggest
problem is the new keyword. He is
taking extra responsibilities of which object needs to be created.
If we give these
responsibilities to other someone else except Order class our problem may be
solved.
See below
code
public class Order:ISaveOrder
{
private ILog oLog;
public Order(ILog iLog)
{
oLog = iLog;
}
public Order()
{
}
public virtual void SaveOrder(decimal price)
{
try
{
int i = 10, j = 0;
i = i / j;
//Save
Order code Goes Here
}
catch (Exception ex)
{
oLog.ErrorHandle(ex.ToString());
}
}
}
class EmailLog:ILog
{
public void ErrorHandle(string ex)
{
//Email
code here
}
}
class smsLog:ILog
{
public void ErrorHandle(string ex)
{
//Sms
code here
}
}
class FileLog : ILog
{
public void ErrorHandle(string ex)
{
//Email
code here
}
}
public class MakeOrder
{
public void MyOrder()
{
List<Order> Orders = new List<Order>();
ISaveOrder oI = new Order(new smsLog());
oI.SaveOrder(10);
}
}
When
you call MyOrder Method, you will get an error, and catch block in Order class call the smsLog class
method.
gud one.
ReplyDeleteThanks buddy .. This article really help me a lot.
ReplyDelete