Solid Principles in Programming
In object-oriented
computer programming, the term SOLID is
a mnemonic acronym for five design principles intended to make
software designs more understandable, flexible and maintainable.
Single responsibility
principle
A class should have only a single responsibility
(i.e. changes to only one part of the software's specification should be able
to affect the specification of the class).
Open/closed principle
"Software entities … should be open for
extension, but closed for modification."
Objects in a program should be replaceable with
instances of their subtypes without altering the correctness of that
program." See also design by contract.
Interface segregation
principle
Many client-specific interfaces are better than
one general-purpose interface.
Dependency inversion
principle
One should "depend upon abstractions, [not]
concretions.”
Single responsibility
principle example:(Without SRP)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Solid_Principles
{
class Program
{
static void Main(string[] args)
{
Customer
customer = new Customer();
customer.Add();
Console.ReadLine();
}
}
public class Customer
{
public void Add()
{
HandleError
handleError = new HandleError();
try
{
//Customer
Data Access Code Here;
}
catch (Exception
error)
{
System.IO.File.WriteAllText(@"c:\test\error.txt",
error.ToString());
}
finally
{
}
}
}
}
Single responsibility
principle example:(With SRP)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Solid_Principles
{
class Program
{
static void Main(string[] args)
{
Customer
customer = new Customer();
customer.Add();
Console.ReadLine();
}
}
public class Customer
{
public void Add()
{
HandleError
handleError = new HandleError();
try
{
//Customer
Data Access Code Here;
}
catch (Exception
error)
{
handleError.LogError(error);
}
finally
{
}
}
}
}
public class HandleError
{
public void LogError(Exception
error)
{
System.IO.File.WriteAllText(@"c:\test\error.txt",
error.ToString());
}
}
Open/closed
principle(Without O/cP)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Solid_Principles
{
class Program
{
static void Main(string[] args)
{
Customer
customer = new Customer();
OpenClosedPricnicples
openClosedPricnicples = new OpenClosedPricnicples();
var result
= openClosedPricnicples.getDiscount(100, 2);
Console.WriteLine(result);
//customer.Add();
Console.ReadLine();
}
}
class OpenClosedPricnicples
{
private int _CustType;
public int CustType
{
get { return _CustType;
}
set {
_CustType = value; }
}
public double getDiscount(double TotalSales, int _CustType)
{
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.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Solid_Principles
{
class Program
{
static void Main(string[] args)
{
OpenClosedPricnicples
openClosedPricnicples = new OpenClosedPricnicples();
var result
= openClosedPricnicples.getDiscount(100);
SilverCustomer
silverCustomer = new SilverCustomer();
var ResultSilver
= silverCustomer.getDiscount(200);
GoldCustomer
goldCustomer = new GoldCustomer();
var ResultGold
= goldCustomer.getDiscount(500);
Console.WriteLine(result);
Console.WriteLine(ResultSilver);
Console.WriteLine(ResultGold);
Console.ReadLine();
}
}
class OpenClosedPricnicples
{
public virtual double getDiscount(double TotalSales)
{
return TotalSales;
}
}
class SilverCustomer :
OpenClosedPricnicples
{
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;
}
}
}
Putting
in simple words the “OpenClosedPricnicples” class is now closed for any new
modification but it’s open for extensions when new customer types are added to
the project.
Liskov Principles (LSP)
L
stands for the Liskov Substitution Principle (LSP) and states that you should
be able to use any derived class in place 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, i.e. that a derived class must be
substitutable for its base class.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Liskov_Principles
{
class Program
{
static void Main(string[] args)
{
Animal
animal = new Dog();
Console.WriteLine(animal.Walk());
Console.WriteLine(animal.Run());
Console.WriteLine(animal.Fly());
Console.WriteLine(animal.MakeNoise());
Console.WriteLine("\n");
Animal
animal1 = new Bird();
Console.WriteLine(animal1.Walk());
Console.WriteLine(animal1.Run());
Console.WriteLine(animal1.Fly());
Console.WriteLine(animal1.MakeNoise());
Console.ReadLine();
}
}
public class Animal
{
public string Walk()
{
return "Move
feet";
}
public string Run()
{
return "Move
feet quickly";
}
public virtual string Fly()
{
return null;
}
public virtual string MakeNoise()
{
return null;
}
}
public class Dog :
Animal
{
public override string MakeNoise()
{
return "Bark";
}
}
public class Bird :
Animal
{
public override string MakeNoise()
{
return "Chirp";
}
public override string Fly()
{
return "Flag
wings";
}
}
}
Interface Segregation
Principle (ISP)
The interface-segregation principle (ISP)
states that no client should be forced to depend on methods it does not
use. ISP splits interfaces that are very large into smaller and more
specific ones so that clients will only have to know about the methods that are
of interest to them. (Wiki)
We can define it in another
way. An interface should be more closely related to the code that uses it than
code that implements it. So the methods on the interface are defined by which
methods the client code needs than which methods the class implements. So
clients should not be forced to depend upon interfaces that they don't use.
Interface Segregation
Principle (Without ISP)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace InterfaceSegregationPrinciples_ISP
{
class Program
{
static void Main(string[] args)
{
Desktop
desktop = new Desktop();
desktop.Monitor();
desktop.WifiConnection();
Laptop
laptop = new Laptop();
laptop.Monitor();
laptop.WifiConnection();
Console.ReadLine();
}
}
public interface IComputer
{
void Monitor();
void WifiConnection();
}
public class Desktop :
IComputer
{
public void Monitor()
{
Console.WriteLine("I
am from MonitorTest");
}
public void WifiConnection()
{
//throw
new NotImplementedException("not Implemented");
}
}
public class Laptop :
IComputer
{
public void Monitor()
{
//throw
new NotImplementedException();
}
public void WifiConnection()
{
Console.WriteLine("I
am from Wifi Connection of Laptop");
}
}
}
Since
the Desktop can’t use the WifiConnection, but implementing IComputer interface
we have to implement the Wificonnection in Desktop class. We need to
provide a concrete method. Here we are forcing the Desktop Class to implement a
WifiConnection method without purpose. This is not a good idea and violating
ISP Principle.
Since we have two
classes Desktop and Laptop. Separate the interface for each classes.
For Desktop Implemented IComputer Interface, for laptop use ILaptop Interface.
We need to divid the responsibilities by segregating the Icomputer, ILaptop
interfaces respectively.
Interface Segregation
Principle (With ISP)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace InterfaceSegregationPrinciples_ISP
{
class Program
{
static void Main(string[] args)
{
Desktop
desktop = new Desktop();
desktop.Monitor();
Labtop
labtop = new Labtop();
labtop.WifiConnection();
Console.ReadLine();
}
}
public interface IComputer
{
void Monitor();
}
public interface ILaptop
{
void WifiConnection();
}
public class Desktop :
IComputer
{
public void Monitor()
{
Console.WriteLine("I
am from MonitorTest");
}
}
public class Labtop :
ILaptop
{
public void WifiConnection()
{
Console.WriteLine("I
am from Wifi Connection of Laptop");
}
}
}
Dependency Inversion
Principle (DIP)
(Microsoft)
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.
This
principle is primarily concerned with reducing dependencies amongst the code
modules. We can think of it as needing the low-level objects to define
contracts that the high-level objects can use, without the high-level objects
needing to care about the specific implementation the low-level objects
provide.
Please
note that the DIP is not quite the same thing as Dependency Injection.
Simple
Example
We
are sending notifications to the client Like Email or SMS.
Example
Dependency Inversion
Principle (Without DIP)
using System;
using
System.Collections.Generic;
using System.Linq;
using System.Text;
namespace
SOLIDPrinciplesDemo
{
//DIP Violation
// Low level Class
public class BankAccount
{
public
string AccountNumber { get; set; }
public
decimal Balance { get; set; }
public
void AddFunds(decimal value)
{
Balance
+= value;
}
public
void RemoveFunds(decimal value)
{
Balance
-= value;
}
}
// High level Class
public class
TransferAmount
{
public
BankAccount Source { get; set; }
public
BankAccount Destination { get; set; }
public
decimal Value { get; set; }
public
void Transfer()
{
Source.RemoveFunds(Value);
Destination.AddFunds(Value);
}
}
/*
Problem with above
design:
1. The high level
TransferAmount class is directly dependent upon the lower level BankAccount
class i.e. Tight coupling.
2. The Source and
Destination properties reference the BankAccount type.So impossible to
substitute other account types unless they are subclasses of BankAccount.
3. Later we want to add
the ability to transfer money from a bank account to pay bills, the BillAccount
class would have to inherit from BankAccount.
As bills would not
support the removal of funds,
3.A. This is
likely to break the rules of the Liskov Substitution Principle (LSP) or
3.B. Require
changes to the TransferAmount class that do not comply with the Open/Closed
Principle (OCP).
4. Any extension
functionality changes be required to low level modules.
4.A. Change in the
BankAccount class may break the TransferAmount.
4.B. In complex
scenarios, changes to low level classes can cause problems that cascade upwards
through the hierarchy of modules.
5. As the software
grows, this structural problem can be compounded and the software can become
fragile or rigid.
6. Without the DIP, only
the lowest level classes may be easily reusable.
7. Unit testing should
be redone when there is a change in high level or low level classes.
8. Time taken process to
change the existing functionality and extending the functionality
*/
//Applying DIP resolves
these problems by removing direct dependencies between classes.
Dependency Inversion
Principle (With DIP)
using System;
namespace HashTable_and_Dictionary
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Dependency
Inversion Principle\n\n");
Console.WriteLine("Balance in
Source Account\n");
ITransfer_Source transfer_Source = new BankAccount();
transfer_Source.AccountNumber =
112233;
transfer_Source.Balance = 5000;
Console.WriteLine("Account Number
: {0} ",
transfer_Source.AccountNumber);
Console.WriteLine("Account
Balance : {0} \n", transfer_Source.Balance);
Console.WriteLine("Balance in
Target Account\n");
ITransfer_Destination
transfer_Destination = new BankAccount();
transfer_Destination.AccountNumber
= 121212;
transfer_Destination.Balance = 0;
Console.WriteLine("Account Number
: {0} ",
transfer_Destination.AccountNumber);
Console.WriteLine("Account
Balance : {0} \n", transfer_Destination.Balance);
Console.Write("Enter amount
to Transfer : ");
int transfer_Amount =
Convert.ToInt32(Console.ReadLine());
Console.WriteLine("\n");
if (transfer_Amount >
transfer_Source.Balance)
{
Console.WriteLine("Insufficient
Amount to Transfer");
Console.ReadLine();
return;
}
TransferAmount
TransferSourceDestination = new TransferAmount();
TransferSourceDestination.Amount =
transfer_Amount;
TransferSourceDestination.Transfer(transfer_Source,
transfer_Destination);
Console.WriteLine("Source Account
Balance : {0} \n ", transfer_Source.Balance);
Console.WriteLine("Target Account
Balance : {0} \n ", transfer_Destination.Balance);
Console.ReadLine();
}
}
public interface ITransfer_Source
{
long AccountNumber { get; set; }
decimal Balance { get; set; }
void RemoveAmount(decimal value);
}
public interface ITransfer_Destination
{
long AccountNumber { get; set; }
decimal Balance { get; set; }
void AddAmount(decimal value);
}
public class BankAccount : ITransfer_Source,
ITransfer_Destination
{
public long AccountNumber { get; set; }
public decimal Balance { get; set; }
public void RemoveAmount(decimal value)
{
Balance -= value;
}
public void AddAmount(decimal value)
{
Balance += value;
}
}
public class TransferAmount
{
public decimal Amount { get; set; }
public void
Transfer(ITransfer_Source transfer_Source, ITransfer_Destination
transfer_Destination)
{
transfer_Source.RemoveAmount(Amount);
transfer_Destination.AddAmount(Amount);
}
}
}
/*
Advantage in above
example after applying DIP:
1. Higher level classes
refer to their dependencies using abstractions, such as interfaces or abstract
classes i.e. loose coupling.
2. Lower level classes
implement the interfaces, or inherit from the abstract classes.
3. This allows new
dependencies can be substituted without any impact.
4. Lower levels classes
will not cascade upwards as long as they do not involve changing the
abstraction.
5. Increases the
robustness of the software and improves flexibility.
6. Separation of high
level classes from their dependencies raises the possibility of reuse of these
larger areas of functionality.
7. Minimized risk to
affect old funtionallity present in Higher level classes.
8. Testing applies only
for newly added low level classes.
9. Though using this
principle implies an increased effort and a more complex code, but it is more
flexible.
Note:
In that case the
creation of new low level objects inside the high level classes(if necessary)
can not be done using the operator new.
Instead, some of the
Creational design patterns can be used, such as Factory Method, Abstract
Factory, Prototype.
The Template Design
Pattern is an example where the DIP principle is applied.
This principle cannot be
applied for every class or every module.
If we have a class
functionality that is more likely to remain unchanged in the future there is
not need to apply this principle.
No comments:
Post a Comment