SOLID is an acronym representing principals of Object Oriented Programming and Design.
The principles of SOLID are guidelines that can be applied while working on an application by providing a framework through which the programmer may refactor the source code until it is both legible and extensible. It is part of an overall strategy of Agile and Adaptive Software Development.
- Single Responsibility
- Open Closed
- Liskov Substitution
- Interface Segregation, and
- Dependency Inversion
Single Responsibility Principal (SRP) This is the first principle of SOLID principles. As per SRP, there should not be more than one reason for a class to change, or a class should always handle single functionality. If you put more than one functionality in a Class using C#, it introduces coupling between two functionalities and even if you change one functionality there is a chance of braking coupled functionality, which require another round of testing to avoid any surprises in production environment.
Example: Below Employee class is build without implementing the SRP and handling two responsibilities. One is to Insert employee data and, the other is to Generate Reports for provided employee. Here, if any changes are required to made for Report generation, then the employee class functionality may also affect leading testing employee also.
public class Employee
{public int Employee_Id { get; set; }public string Employee_Name { get; set; }
// To Insert data into Employee tablepublic bool InsertIntoEmployeeTable(Employee emp){return true;
}
// To Generate Report of provided employeepublic void GenerateReport(int empId){// Report generation with employee data
}}
As per SRP, one class should handle only one responsibility. To incorporate this principal, we will create another class to handle the Reporting functionality, so that Employee class will handle only one responsibility and any change in report generation functionality will not affect the Employee class.
After implementing Single Responsibility Principal, above implementation will look like below:
public class Employee
{public int Employee_Id { get; set; }public string Employee_Name { get; set; }
// To Insert data into Employee tablepublic bool InsertIntoEmployeeTable(Employee emp){return true;
}}
public class ReportGeneration
{// To Generate Report of provided employeepublic void GenerateReport(int empId){// Report generation with employee data
}}
Open Closed Principle This is the second principle from SOLID principles. In object-oriented programming, the open/closed principle states “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”; that is, such an entity can allow its behavior to be extended without modifying its source code.
Example: Below code snippet explains before implementing Open-Closed principal.
public class ReportGeneration
{
public string ReportType { get; set; }public void GenerateReport(Employee emp){if (ReportType == "CRS")
{
// Report generation with employee data in Crystal Report.
}
if (ReportType == "PDF")
{
// Report generation with employee data in PDF.
}
}}
In above example, if we want to introduce another report type like SSRS or Excel, then this class needs to be modified with another If condition to handle the new report type(s). According to Open-Closed principal, Any class should be Open for extension but Closed for modification.
After implementing Open Closed principal, above implementation will look like below, and if you want to introduce a new report type, then we just need to inherit the new class from IReportGeneration. So IReportGeneration is open for extension but closed for modification.
public class IReportGeneration
{
public virtual void GenerateReport(Employee emp){// From base
}}
public class CrystalReportGeneraion : IReportGeneration
{
public override void GenerateReport(Employee emp){// Generate crystal report.
}}
public class PDFReportGeneraion : IReportGeneration
{
public override void GenerateReport(Employee emp){// Generate PDF report.
}}
The Liskov Substitution Principle (LSP) is a particular definition of a subtyping relation, called (strong) behavioral subtyping, that was initially introduced by Barbara Liskov in a 1987 conference keynote address entitled Data abstraction and hierarchy
- if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program In order to follow this principle we need to make sure that the subtypes respect the parent class.
public abstract class Employee
{
public virtual string GetProjectDetails(int empId){return "Base Project";
}public virtual string GetEmployeeDetails(int empId){return "Base Employee";
}}
public class CasualEmployee : Employee
{
public override string GetProjectDetails(int empId){return "Child Project";
}// May be for contractual employee we do not need to store the details into database.public override string GetEmployeeDetails(int empId){return "Child Employee";
}}
public class ContractualEmployee : Employee
{public override string GetProjectDetails(int empId){return "Child Project";
}// May be for contractual employee we do not need to store the details into database.public override string GetEmployeeDetails(int empId){throw new NotImplementedException();
}}
In above example, when we try to call ContractualEmployee.GetEmployeeDetails(empId) will return "Not Implemented" exception by violating LSP. The Liskov Substitution Principle implementation leads creating two interfaces to inherit the base differently for Employee and Contractual classes as below:
public interface IEmployee
{
string GetEmployeeDetails(int empId);}
public interface IProject
{
string GetProjectDetails(int empId);}
public class CasualEmployee : IEmployee
{
// May be for contractual employee we do not need to store the details into database.public override string GetEmployeeDetails(int empId){return "Child Employee";
}}
public class ContractualEmployee : IProject
{public override string GetProjectDetails(int empId){return "Child Project";
}}
Interface Segregation Principle (ISP) states that no client should be forced to depend on methods it does not use.
- ISP splits interfaces which 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.
- ISP is intended to keep a system decoupled and thus easier to refactor, change, and redeploy.
public interface IEmployeeDatabase
{
bool AddEmployeeDetails();bool ShowEmployeeDetails(int empId);}
When we have above employee interface used for both employee and non-employee classes with above two methods, we are breaking ISP by forcing non-employee class to show their details. After implementing Interface Segregation Principle, showEmployeeDetails() will be moved to another interface's responsibility as below:
public interface IAddOperation
{
bool AddEmployeeDetails();}
public interface IGetOperation
{
bool ShowEmployeeDetails(int empId);}
Dependency Inversion principle (DIP) refers to a specific form of decoupling software modules.It states:
- 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.
Here is an example without implementing DIP:
public class Email
{
public void SendEmail(){// code to send mail
}}
public class Notification
{
private Email _email;public Notification(){_email = new Email();
}public void PromotionalNotification(){_email.SendEmail();
}}
In the above example, clearly Notification class will work good for the Email notifications. This scenario is tightly coupled as, if we want to introduce another type of notification, the Notification class also needs to be modified. We can follow below steps to make this as loosely coupled, here is how, as first step change the above class to use interface as below:
public interface IMessenger
{
void SendMessage();}
public class Email : IMessenger
{
public void SendMessage(){// code to send email }}
public class SMS : IMessenger
{
public void SendMessage(){// code to send SMS
}}
public class Notification
{
private IMessenger _iMessenger;public Notification(){_ iMessenger = new Email();
}public void DoNotify(){_ iMessenger.SendMessage();
}}
Still Notification class is dependent on Email class. By using Dependency Injection (DI) we can make it completely loosely coupled. There are 3 ways to implement DI as below:
Constructor Injection
public class NotificationProperty Injection
{
private IMessenger _iMessenger;public Notification(Imessenger pMessenger){_ iMessenger = pMessenger;
}public void DoNotify(){_ iMessenger.SendMessage();
}}
public class NotificationMethod Injection
{
private IMessenger _iMessenger;public Notification(){}public IMessenger MessageService{private get;
set { _ iMessenger = value; }
}public void DoNotify(){_ iMessenger.SendMessage();
}}
public class NotificationNow, the Notification class is completely loosely coupled and this is how Dependency Inversion Principle helps us to write loosely coupled code which is highly maintainable and less error prone.
{
public void DoNotify(IMessenger pMessenger){pMessenger.SendMessage();
}}
With this I am concluding the illustration. Feel free to share your feedback. Happy Programming !!!
No comments:
Post a Comment