SOLID is an acronym for basic object oriented programming & design principals. Applying these principle on system makes system easy to maintain and extendable with time.
MarutiAutomatic.GearShiftingChamber(); //Problem
}
Now Copier like SmartCanonCopier those do not have one or more features out of given in ISmartCopier, also need to implement all methods of ISmartCopier, this is violation of ISP because it says “No client should be forced to depend on methods it does not use”
- Single Responsibility Principle (SRP)
- Open Closed Principle (OCP)
- Liskov Substitution Principle (LSP)
- Interface Segregation Principle (ISP)
- Dependency Inversion Principle (DIP)
Also refer nice article on https://www.syncfusion.com/blogs/post/mastering-solid-principles-csharp
- Single Responsibility Principle (SRP): Single responsibility principle says a software module / class should have only one reason to modify. It means if any class having more than one reasons to modify, this is violation of SRP.
public class Circle
{
private int _radius;
public int Radius
{
get
{
return _radius;
}
set
{
value = _radius;
}
}
public int Area()
{
return Radius * Radius * 22 / 7;
}
public string PrintHTMLArea()
{
return "<h2>" + Area().ToString() + "</h2>";
}
}
Above class contains two responsibility
Calculating Area
Printing Area
If properties need to add/modify or print method need to be modified or need to add new print method, class need to be updated. So this is violation of SRP, entity class should have only entity specific things like variables / properties.
Calculating Area
Printing Area
If properties need to add/modify or print method need to be modified or need to add new print method, class need to be updated. So this is violation of SRP, entity class should have only entity specific things like variables / properties.
- Open Closed Principle (OCP): It says a software module / class should be open for extension but close for modification.
In below functionality code is open for extension but close for modification.
Allowed Extension: Any new shape class like Triangle can be added
Modification not required: Area of triangle will be calculated by clsCalculateArea without modifying the clsCalculateArea class
Allowed Extension: Any new shape class like Triangle can be added
Modification not required: Area of triangle will be calculated by clsCalculateArea without modifying the clsCalculateArea class
internal abstract class absShape
{
internal abstract double Area();
}
internal class Circle : absShape
{
internal int Radius
{
get;
set;
}
internal override double Area()
{
return Radius * Radius * 22 / 7;
}
}
internal class Square : absShape
{
internal int SideLength
{
get;
set;
}
internal override double Area()
{
return SideLength * SideLength;
}
}
internal class clsCalculateArea
{
internal static double TotalArea(List<absShape> lstShapes)
{
double _totalArea = 0;
foreach (absShape shape in lstShapes)
{
_totalArea = _totalArea +
shape.Area();
}
return _totalArea;
}
}
- Loskov Substitution Principle (LSP): It says child class should be substitutable for its parent. A child class should never break types definitions of its base type.
If a child class says, I will not implement or override a member of base type (class/interface)
If a child class says, I've not required a member of base type (class/interface)
It means there is violation of LSP, because we can't use base type in place of the child class in that scenario.
MarutiAutomatic.GearShiftingChamber(); //Problem
Class CarMarutiAutomatic doesn't required interface member GearShiftingChamber(), so if we are using base type ICar in place of CarMarutiAutomatic and calling GearShiftingChamber() It will through not implemented exception.
Source Code :
public interface ICar
{
string ModelName { get; set; }
void StartEngine();
void Accelarate();
void GearShiftingChamber();
}
public class CarMarutiDSL : ICar
{
public string ModelName { get; set; }
public void StartEngine()
{
//Starting
meachanism for CarMarutiDSL
}
public void Accelarate()
{
//Accelarating
meachanism for CarMarutiDSL
}
public void GearShiftingChamber()
{
//GearShifting
meachanism for CarMarutiDSL
}
}
public class CarMarutiLXV : ICar
{
public string ModelName { get; set; }
public void StartEngine()
{
//Starting
meachanism for CarMarutiLXV
}
public void Accelarate()
{
//Accelarating
meachanism for CarMarutiLXV
}
public void GearShiftingChamber()
{
//GearShifting
meachanism for CarMarutiLXV
}
}
public class CarMarutiAutomatic : ICar
{
public string ModelName { get; set; }
public void StartEngine()
{
//Starting
meachanism for CarMarutiAutomatic
}
public void Accelarate()
{
//Accelarating
meachanism for CarMarutiAutomatic
}
public void GearShiftingChamber()
{
//CarMarutiAutomatic
is automatic so there is no gearshiting meachanism required
throw new NotImplementedException();
}
}
Client code:
static void Main(string[] args)
{
ICar MarutiDSL = new CarMarutiDSL();
MarutiDSL.StartEngine();
MarutiDSL.ModelName = "MarutiDSL swift1200";
MarutiDSL.Accelarate();
MarutiDSL.GearShiftingChamber();
ICar MarutiLXV = new CarMarutiLXV();
MarutiLXV.StartEngine();
MarutiLXV.ModelName = "MarutiLXV celario100";
MarutiLXV.Accelarate();
MarutiLXV.GearShiftingChamber();
ICar MarutiAutomatic = new CarMarutiAutomatic();
MarutiAutomatic.StartEngine();
MarutiAutomatic.ModelName = "MarutiDSL celarioAuto";
MarutiAutomatic.Accelarate();
}
Solution:
public interface ICar
{
string ModelName { get; set; }
void StartEngine();
void Accelarate();
}
internal interface IGearChamberCar : ICar
{
void GearShiftingChamber();
}
public class CarMarutiDSL : IGearChamberCar
{
public string ModelName { get; set; }
public void StartEngine()
{
//Starting
meachanism for CarMarutiDSL
}
public void Accelarate()
{
//Accelarating
meachanism for CarMarutiDSL
}
public void GearShiftingChamber()
{
//GearShifting
meachanism for CarMarutiDSL
}
}
public class CarMarutiLXV : IGearChamberCar
{
public string ModelName { get; set; }
public void StartEngine()
{
//Starting
meachanism for CarMarutiLXV
}
public void Accelarate()
{
//Accelarating
meachanism for CarMarutiLXV
}
public void GearShiftingChamber()
{
//GearShifting
meachanism for CarMarutiLXV
}
}
public class CarMarutiAutomatic : ICar
{
public string ModelName { get; set; }
public void StartEngine()
{
//Starting
meachanism for CarMarutiAutomatic
}
public void Accelarate()
{
//Accelarating
meachanism for CarMarutiAutomatic
}
}
Client code:
static void Main(string[] args)
{
ICar MarutiDSL = new CarMarutiDSL();
MarutiDSL.StartEngine();
MarutiDSL.ModelName = "MarutiDSL swift1200";
MarutiDSL.Accelarate();
MarutiDSL.GearShiftingChamber();
ICar MarutiLXV = new CarMarutiLXV();
MarutiLXV.StartEngine();
MarutiLXV.ModelName = "MarutiLXV celario100";
MarutiLXV.Accelarate();
MarutiLXV.GearShiftingChamber();
ICar MarutiAutomatic = new CarMarutiAutomatic();
MarutiAutomatic.StartEngine();
MarutiAutomatic.ModelName = "MarutiDSL celarioAuto";
MarutiAutomatic.Accelarate();
- Interface Segregation Principle (ISP): It says 'no client should be forced to depend on methods it does not use'. ISP is extension of LSP.
Or “do not force any client to implement an interface which is irrelevant to them“.
What features a smart Copier can have copier, printer, fax, phone, scan etc.
#region Problem area
public interface ISmartCopier
public interface ISmartCopier
{
public void Print();
public void Copy();
public void Fax();
public void Call();
public void Scan();
}
//Features Print, Copy, Fax, Call, Scan
public class SmartXeroxCopier : ISmartCopier
{
public void Print()
{
}
public void Copy()
{
}
public void Fax()
{
}
public void Call()
{
}
public void Scan()
{
}
}
//Features Print, Copy, Fax
public class SmartCanonCopier : ISmartCopier
{
public void Print()
{
}
public void Copy()
{
}
public void Fax()
{
}
public void Call()
{
throw new NotImplementedException();
}
public void Scan()
{
throw new NotImplementedException();
}
#endregion
#endregion
Problem:
What happens if a class forced to implement methods it does not use.
Large interfaces should be divided into smaller and more specific pieces, these small specific interfaces called Role interfaces, because they are specific for a specific role.
What happens if a class forced to implement methods it does not use.
- Client implements unnecessary methods those it does not use
- Need to compile all methods every time including unused methods
- Violation of LSP: unused method calling using parent (interface/base class) through NotImplementedException or no output
- Client may get confused with bunch of unnecessary methods
Large interfaces should be divided into smaller and more specific pieces, these small specific interfaces called Role interfaces, because they are specific for a specific role.
#region Solution area
//Below given are Role interfaces
public interface IPrint
{
public void Print();
}
public interface ICopy
{
public void Copy();
}
public interface IFax
{
public void Fax();
}
public interface ICall
{
public void Call();
}
public interface IScan
{
public void Scan();
}
//Features Print, Copy, Fax, Call, Scan
public class SmartXeroxCopier : IPrint, ICopy, IFax, ICall, IScan
{
public void Print()
{
}
public void Copy()
{
}
public void Fax()
{
}
public void Call()
{
}
public void Scan()
{
}
}
//Features Print, Copy, Fax
public class SmartCanonCopier : IPrint, ICopy, IFax
{
public void Print()
{
}
public void Copy()
{
}
public void Fax()
{
}
}
//Features Copier
public class SamsungCopier : ICopy
{
public void Copy()
{
}
}
#endregion
- Dependency Inversion Principle (DIP):
Before discussing this be clear that Dependency Inversion Principle and Dependency injection are different concepts.
Dependency Inversion Principle says
- High level module should not depend on low level module rather both should depend on abstraction
- Abstraction should not depend on details, rather details should depend on abstraction
This principle focuses on decoupling high-level modules from low-level modules by introducing an abstraction layer, with the use of interfaces or abstract classes and reducing direct dependencies between classes.
This could be explained with an example where information flow goes starts from one low level module to high level module to another low level module.
Example having violation of DIP & solution for the violation
Source Code:
- Violation: High level class Job depends on low level classes KeyBoardReader & PrinterWriter. Problem - If in future requirement comes (or a client of library wants one new type writer) to add one new type writer let say FileWriter than we need to change library class Job
public class Job
{
KeyBoardReader reader;
PrinterWriter writer;
public void DoJob()
{
reader = new KeyBoardReader();
writer = new PrinterWriter();
writer.Write(reader.Read());
}
}
public class KeyBoardReader
{
public string Read()
{
return Console.ReadLine();
}
}
public class PrinterWriter
{
public void Write(string text)
{
Console.WriteLine("Writing to
printer {0}", text);
}
}
class ClientDIP
{
static void Main(string[] args)
{
Job objJob = new Job();
objJob.DoJob();
Console.ReadLine();
}
}
- Solution : High level module interacts using interface with low level modules, it remove dependency of high level module on low level module.
public interface IReader
{
string Read();
}
public interface IWriter
{
void Write(string text);
}
public class Job
{
IReader reader;
IWriter writer;
public Job(IReader _reader, IWriter _writer)
{
this.reader = _reader;
this.writer = _writer;
}
public void DoJob()
{
writer.Write(reader.Read());
}
}
public class KeyBoardReader : IReader
{
public string Read()
{
return Console.ReadLine();
}
}
public class PrinterWriter : IWriter
{
public void Write(string text)
{
Console.WriteLine("Writing to
printer {0}", text);
}
}
public class FileWriter : IWriter
{
public void Write(string text)
{
Console.WriteLine("Writing to
file {0}", text);
}
}
class ClientDIP
{
static void Main(string[] args)
{
IReader reader = new KeyBoardReader();
IWriter writer = new PrinterWriter();
Job objJob = new Job(reader, writer);
objJob.DoJob();
Console.ReadLine();
writer = new FileWriter();
objJob = new Job(reader, writer);
objJob.DoJob();
Console.ReadLine();
}
}
Why we need SOLID principles?
- Code maintainability: Promote clean, organized, and maintainable codebases. By using these principles, developers ensure that each component of their code has a clear purpose and well-defined responsibilities. This clarity simplifies maintenance tasks, make it easier to understand, update, and modify code without causing unintended side effects elsewhere.
- Future scalability: Allows for more adaptable and extensible software systems. Developers should design code that can be added to but not modified, allowing others to introduce new features or functionalities without altering existing code. This scalability is essential for accommodating future changes and expanding the system’s capabilities without compromising stability.
- Flexibility in development: Encourages flexibility in development by providing a framework that supports iterative and incremental changes. This enables developers to introduce new functionalities, fix issues, or refactor codebases more efficiently, minimizing risks associated with unintended consequences or regressions.
- Improved collaboration: Encourages a modular and structured approach to development. This organization enhances collaboration among team members as it leads to well-defined interfaces and clear responsibilities, making it easier for different team members to work on distinct parts of the codebase simultaneously.


No comments:
Post a Comment