What is SOLID principle or architecture ?
S.O.L.I.D is a software design principles or we can say it is OOD that is object oriented design.These principles helps programmers to set of guidelines to avoid bad software design.
What is the role of a software architect in software development.Basically a software architect is responsible to provide a pattern on which programmers have to work.
Every software or application must be fixable and expandable,means whenever any new functionality or module needs to be added,then it should be easily added without much more efforts.
In simple terms an application design must be altered easily for every change request or new feature request. After some time we might need to put in a lot of effort, even for simple tasks and it might require a full working knowledge of the entire system. But we can't blame change or new feature requests.
SOLID principles are set of design principles that enable a way to manage the problems under software design.Robert C. Martin introduced these principles in the 1990's. These principles provide us ways to move from tightly coupled code and little encapsulation to the desired results of loosely coupled and encapsulated real needs of a business properly. SOLID is an acronym of the following.
S: Single Responsibility Principle (SRP)
O: Open closed Principle (OSP)
L: Liskov substitution Principle (LSP)
I: Interface Segregation Principle (ISP)
D: Dependency Inversion Principle (DIP)
S- SRP (Single responsibility principle)
SRP principle says that every class, or similar structure, in your code should have only one responsibility.It means that whatever a class does,it should have a single job.If you have create a Employee class then it must perform employee related activities rather than doing additional task like error logging,email sending etc.
Here separation of concern comes into the picture,means separate out the entity based on there responsibility that will help to decouple the software application.
Let look into the problem then we will try to understand SRP in best possible way.
Let look into the problem then we will try to understand SRP in best possible way.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Employee { public void AddEmployee() { try { // logic to add employee into database } catch (Exception ex) { System.IO.File.WriteAllText( @"d:\ErrorLog.txt" , ex.ToString()); } } } |
If you look into above example,you will find that Employee class is perform AddEmplyee activity that is responsibility of this class but what about Error logging (In catch block), this is not responsibility of an Employee class.
So what is the problem with this code.Let assume that there are many class that are doing error logging and if there is a change in error logging logic then we need to change that logic everywhere.
Below example is the solution by assigning a single responsibility to Employee class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class Customer { public void Add() { try { // Database code goes here } catch (Exception ex) { ErrorLogging.LogError(ex.ToString()); } } } class ErrorLogging { public static void LogError( string Log) { System.IO.File.WriteAllText( @"c:\Error.txt" , Log); } } |
O - Open/Closed Principle
In simple terms it states that application should be open for extension and closed for modification.
We need to design our module/class in such a way that the new functionality can easily be added only when new requirements are generated. "Closed for modification" means already developed class or module can not be altered that has been gone through unit testing. We should not alter it until we find bugs. As it says, a class should be open for extensions, we can use inheritance to do this. Okay, let's take an example to understand it.
1 2 3 4 5 6 7 8 9 | public class Customer { public double PerdaySalary; } public class TotalSalary { public double CalculateTotalSalary(Customer customer) { return 30 * customer.PerdaySalary; } } ]] |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class FullTimeCustomer { public double PerdaySalary; } public class PartTimeCustomer { public double PerHourSalary; } public class TotalSalary { public double CalculateTotalSalary(Object entity) { if (entity is FullTimeCustomer) { return 30 * entity.PerdaySalary; } else { // assuming per day work is of 9 hours return 9 * 30 * entity.PerHourSalary; } } } |
Have you imagine that every time we introduce new Customer type we need to modify TotalSalary class which is already implemented and test(assume) then why to change this class every time.(need software testing again and again).Isn't it a bad situation? Here comes the Open/Closed principle.
What open/closed principle say, that classes or modules that are already implemented that are closed for modification but your application should be open to add new changes/functionalities.
How can we make our design to avoid this situation? We can do this by introducing a abstractions for dependencies, such as interfaces or abstract classes, rather than using concrete classes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public abstract class CustomerType { public abstract double CalculateTotalSalary(); } public class FullTimeCustomer: CustomerType { public double PerdaySalary { get ; set ; } public override double Calculate() { return 30 * PerdaySalary; } } public class PartTimeCustomer: CustomerType { public double PerHourSalary { get ; set ; } public override double Calculate() { return 9 * 30 * entity.PerdaySalary; } } public class TotalSalary { public double FullMonthSalary(Object entity) { entity.Calculate(); } } |
Now look at the above code.It has both SRP and OCP. Whenver there is new type introduce you just need to work on that type only,no need to change class that are already implemented.
L - Liskov substitution Principle
Let's dive into liskov's substitution principle,which says that user should be able to use derived class rather than it's base class and it must behave in a same way without any modification in base class.In simple way,derived class will treat as a base class.(as a substitute).
Liskov's principle is an extension to the Open Close Principle and it means that user must ensure that new derived classes inherits the base classes without changing their behavior. Let's play with an example that is quite popular (Example of square and rectangle) that violates Liskov's principle(LSP).
In mathematics conceptually a square is a rectangle whose side and width are same.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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 FindAreaArea() { return _width * _height; } } public class Square : Rectangle { public override void SetWidth( int width) { _width = width; _height = width; } public override void SetHeight( int height) { _height = height; _width = height; } } |
So to calculate Area of rectangle and square,we use below code.
1 2 3 4 5 6 7 8 9 10 11 12 | public Rectangle RectangleSet() { return new Square(); } public void CalculateAreaOfRectangle() { Rectangle r = RectangleSet(); // return Rectangle object r.SetWidth(10); r.SetHeight(5); r.getArea(); } |
So What is wrong with above code?
If you notice LSP principle which stats that "user must make sure that new derived classes inherits the base classes without changing their behavior" .
"Here square classes inherits the rectangle but it is changing it's behavior"
If you notice LSP principle which stats that "user must make sure that new derived classes inherits the base classes without changing their behavior" .
"Here square classes inherits the rectangle but it is changing it's behavior"
Ok Lets look into below code
Width=Height is must be rule for Square not for Rectangle .So when Square object returns from RectangleSet() before calling CalculateArea() user assigns r.SetHeight(10) and r.SetWidth(5) and waiting for a result 50 but getting 25. It changes the result that user expecting. But programmer was expecting the result of Rectangle area.
So here violation of Liskov's substitution principle occurs.
Now the solution is to manage the class inheritance hierarchies correctly. Lets introduce another class.
Now change the inheritance hierarchies by making it base class.
Let do what that we actually want.Look into below code-
1 2 3 4 5 6 7 8 9 10 11 12 13 | public override void SetWidth( int width) { _width = width; _height = width; //Change the behavior here by assigning the width to Rectangle _height } public override void SetHeight( int height) { _height = height; _width = height; //Change the behviour here by assigning the height to Rectangle _width } |
Width=Height is must be rule for Square not for Rectangle .So when Square object returns from RectangleSet() before calling CalculateArea() user assigns r.SetHeight(10) and r.SetWidth(5) and waiting for a result 50 but getting 25. It changes the result that user expecting. But programmer was expecting the result of Rectangle area.
So here violation of Liskov's substitution principle occurs.
Now the solution is to manage the class inheritance hierarchies correctly. Lets introduce another class.
1 2 3 4 5 6 7 8 9 | public class Shape { public virtual int Height { get ; set ; } public virtual int Width { get ; set ; } public int getArea() { return Height * Width; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | public class Rectangle :Shape { 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 : Shape { 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; } } public Shape ShapeSet() { return new Square(); } public void AreaofShape() { Shape r = ShapeSet(); r.Height=10; r.Width=5; r.CalulateArea(); } |
Let do what that we actually want.Look into below code-
1 2 3 4 5 6 7 8 9 | ` Shape r = new Rectangle(); r.Height=10; r.Width=5; r.getArea(); Shape r = new Square(); r.Height = 10; r.Width = 20; r.getArea(); |
I - Interface Segregation Principle (ISP)
If you know that method declared in interface are mandatory to implement in those classes that implementing the interface,Interface segregation principle is all out implementing an interface.
Interface segregation principle "User should not force to implement interface if they don;t require it".To do that instead a single interface divide the interface into small interfaces(each small interface act like a small individual module).
Let's look into below example -
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public Interface IManager { void CreateRole(); void EditRole(); } public class Manager : IManager { public void CreateRole() { //actual implementation } public void EdirRole() { //actual implementation } } |
Here is the code that violates ISP.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public Interface IManager { void CreateRole(); void EditRole(); } public class Manager : IManager { public void CreateRole() { //actual implementation } public void EditRole() { //actual implementation } } public class SubManager : IManager { public void CreateRole() { throw new Exception( "SubManager can not create new role" ); // Here we need to forefully implement the method as // per interface rule,but there is no use at all } public void EditRole() { //actual implementation } } |
So to overcome with this,what we need to do this - Split the interface into modules as required.Here there are two entities Manager and Sub-Manager so we create interface according to there needs.
Here is the example in which we have divide one interface into 2 parts -
1 2 3 4 | public Interface IManager { void CreateRole(); } |
1 2 3 4 | public Interface ISubManager { void EditRole(); } |
1 2 3 4 5 6 7 8 9 10 11 | public class Manager : IManager,ISubManager { public void CreateRole() { //actual implementation } public void EditRole() { //actual implementation } } |
1 2 3 4 5 6 7 | public class SubManager : ISubManager { public void EditRole() { //actual implementation } } |
Here we have separated the functionalities and distributed then to multiple interfaces and overcome the ISP violation.
D: Dependency Inversion Principle
According to Dependency Inversion Principle (DIP) high-level classes or modules should not depend upon classes or modules. Both should depend upon abstractions.
Secondly,abstractions should not depend upon details. Details should depend upon abstractions.High-level modules or classes implements the business rules or logic of an application. Low-level modules/classes deal with more detailed operations/functionalities, in other words they may deal with writing information to databases or passing messages to the operating system or services.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public class ErrorLogWriter { public void Write( string message) { //Write to event log here } } public class WriteFile { ErrorLogWriter error_writer = null ; public void Notify( string message) { if (error_writer == null ) { error_writer = new ErrorLogWriter(); } error_writer.Write(message); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public interface ILogger { void Write(); } public class ErrorLogWriter : ILogger { public void Write( string message) { //Write to event log here } } public class WriteFile { ILogger error_writer = null ; public void Notify( string message) { if (error_writer == null ) { error_writer = new ErrorLogWriter(); } error_writer.Write(message); } } |
Hope you have like this post.Please provide your feedback.
Thanks.
What is SOLID principle or architecture ?
Reviewed by CodiBucket
on
00:45
Rating:

No comments: