Table of Content
- 1. Single Responsibility Principle (SRP)
- 2. Open-Closed Principle (OCP)
- 3. Liskov Substitution Principle (LSP)
- 4. Interface Segregation Principle (ISP)
- 5. Dependency Inversion Principle (DIP)
- 6. Conclusion
In C#, SOLID design principles are basic design principles. SOLID Principles enable us to overcome most of the design problems. In the 1990s, Robert Martin compiled these principles. These principles provided us with many ways to overcome tight coupled code and least encapsulation.
Usually, Developers start the application building process with good and clean designs according to their experience. But, as time passes, applications might develop bugs. The application has to be changed for every new feature or change request. After a long time, we might need to put more effort, even for small tasks and it might require a lot of time and energy. But as they are part of software development, we cannot refuse them as well.
Here are few design issues which are causing damage in any software in most cases:
- When we put more stress on specific classes by assigning them more responsibilities to them which are not related to that class.
- When we force the class to depend on each other or when they are tightly coupled.
- When we use duplicate code in the application
To overcome these issues, we can implement the following tactics:
- We must choose the right architecture for the application
- We must follow the design principles made by experts.
- We must choose the correct design patterns to build the software according to its specifications and needs.
Here, SOLID stands for as shown below:
S = Single Responsibility Principle
O = Open Closed Principle
L = Liskov Substitution Principle
I = Interface Segregation Principle
D = Dependency Inversion Principle
Single Responsibility Principle (SRP):
As per the concepts of the Single Responsibility Principle, every module of the software must have only a single reason to change. Everything in that particular class
This means, that every single class or class-like structure in our code must have only one job to perform. All the things in that class must be related to one single purpose. Our class must not be multifunctional at all.
The Single Responsibility Principle represents a way of recognizing classes at the App’s design phase making you think of all the ways a class can change. When you see an excellent clarity of operation requests, a fine separation of responsibilities occurs.
Let us comprehend this with an example.
public class UserService { EmailService _emailService; DbContext _dbContext; public UserService(EmailService aEmailService, DbContext aDbContext) { _emailService = aEmailService; _dbContext = aDbContext; } public void Register(string email, string password) { if (!_emailService.ValidateEmail(email)) throw new ValidationException("Email is not an email"); var user = new User(email, password); _dbContext.Save(user); emailService.SendEmail(new MailMessage("myname@mydomain.com", email) {Subject="Hi. How are you!"}); } } public class EmailService { SmtpClient _smtpClient; public EmailService(SmtpClient aSmtpClient) { _smtpClient = aSmtpClient; } public bool virtual ValidateEmail(string email) { return email.Contains("@"); } public bool SendEmail(MailMessage message) { _smtpClient.Send(message); } }
Open-Closed Principle (OCP):
The Open/closed principle says "A software module/class is opened for the extension and closed for editing".
Here, "Open for extension" means that we should design our module/class so that the new feature can only be added when new requirements are generated. Here, "Open for extension" means that we should design our module/class so that the new functionality can only be added when new requirements are created. We should never tamper with it until we find some bugs. As mentioned, a class should be open for extensions, we can use inheritance for this. OK, let's take one example.
public class Rectangle: Shape { public double Height {get;set;} public double Width {get;set;} public override double Area() { return Height * Width; } } public class Circle: Shape { public double Radius {get;set;} public override double Area() { return Radius * Radius * Math.PI; } } public class AreaCalculator { public double TotalArea(Shape[] arrShapes) { double area=0; foreach(var objShape in arrShapes) { area += objShape.Area(); } return area; } }
Now our code tracks SRP and OCP at once. When you introduce a new form deriving from the abstract class "Shape", you do not need to change the class "AreaCalculator".
Liskov Substitution Principle (LSP):
The Liskov Substitution Principle (LSP) states that "you should be able to use any derived class instead of a parent class and have it behave in the same manner without modification". It guarantees that a derived class does not affect the behavior of the parent class, in other words, that a derived class must be substitutable for its basic class.
This principle is just an extension of the Open-Closed Principle and it means that we need to make sure that the new derived classes extend the basic classes without changing their behavior. I will explain it by employing a concrete example that violates the LSP.
A dad is a doctor and his son wants to be a cricket player. So here the son cannot replace his father even if the two belong to the same family hierarchy.
public class SqlFileManager { public string GetTextFromFiles(ListaLstReadableFiles) { StringBuilder objStrBuilder = new StringBuilder(); foreach(var objFile in aLstReadableFiles) { objStrBuilder.Append(objFile.LoadText()); } return objStrBuilder.ToString(); } public void SaveTextIntoFiles(List aLstWritableFiles) { foreach(var objFile in aLstWritableFiles) { objFile.SaveText(); } } }
Looking for Trusted C# Development Company ?
For Your Business?
Interface Segregation Principle (ISP):
The principle of segregation of interfaces stipulates that customers should not be forced to implement interfaces they do not use. Rather than a fat interface, many small interfaces are preferred depending on method groups, each serving as a sub-module.
It may be defined differently. An interface should be closer to the code which uses it than to the code which implements it. So the methods of the interface are defined by the methods that the client code needs instead of the methods that the class implements. As a result, clients should not be forced to depend on interfaces they do not use.
As with classes, each interface needs to have a specific purpose/responsibility (see SRP). You should not have to implement an interface when your object does not share this goal. The larger the interface, the more likely it is that it includes methods that not every implementer can do. That's the essence of the Interface Segregation Principle. Let's start with an example.
public class TeamLead: IProgrammer, ILead { public void AssignTask() { //Code to assign a Task } public void CreateSubTask() { //Code to create a sub-task from a task. } public void WorkOnTask() { //code to implement to work on the Task. } }
Dependency Inversion Principle (DIP):
The Dependency Inversion Principle says that all the high-level modules and classes must not depend on low-level modules and classes. All must depend upon abstractions. Also, abstractions must not depend upon details of it, instead, they should depend upon abstractions.
public class DataExporter { public void ExportDataFromFile() { ExceptionLogger _exceptionLogger; try { //code to export data from files to database. } catch(IOException ex) { _exceptionLogger = new ExceptionLogger(new DbLogger()); _exceptionLogger.LogException(ex); } catch(SqlException ex) { _exceptionLogger = new ExceptionLogger(new EventLogger()); _exceptionLogger.LogException(ex); } catch(Exception ex) { _exceptionLogger = new ExceptionLogger(new FileLogger()); _exceptionLogger.LogException(ex); } } }
Conclusion
In this article, we discussed the C# SOLID principles for better coding standards and how to make it as fruitful as possible. We also saw a few examples to deeply understand these concepts.
A Comprehensive Guide on C# SOLID Principles Table of Content 1. Single Responsibility Principle (SRP) 2. Open-Closed Principle (OCP) 3. Liskov Substitution Principle (LSP) 4. Interface Segregation Principle (ISP) 5. Dependency Inversion Principle (DIP) 6. Conclusion In C#, SOLID design principles are basic design principles. SOLID Principles enable us to overcome most of the design problems. In the 1990s, Robert Martin compiled these principles. These principles provided us with many ways to overcome tight coupled code and least encapsulation. Usually, Developers start the application building process with good and clean designs according to their experience. But, as time passes, applications might develop bugs. The application has to be changed for every new feature or change request. After a long time, we might need to put more effort, even for small tasks and it might require a lot of time and energy. But as they are part of software development, we cannot refuse them as well. Here are few design issues which are causing damage in any software in most cases: When we put more stress on specific classes by assigning them more responsibilities to them which are not related to that class. When we force the class to depend on each other or when they are tightly coupled. When we use duplicate code in the application To overcome these issues, we can implement the following tactics: We must choose the right architecture for the application We must follow the design principles made by experts. We must choose the correct design patterns to build the software according to its specifications and needs. Here, SOLID stands for as shown below: S = Single Responsibility Principle O = Open Closed Principle L = Liskov Substitution Principle I = Interface Segregation Principle D = Dependency Inversion Principle Single Responsibility Principle (SRP): As per the concepts of the Single Responsibility Principle, every module of the software must have only a single reason to change. Everything in that particular class This means, that every single class or class-like structure in our code must have only one job to perform. All the things in that class must be related to one single purpose. Our class must not be multifunctional at all. The Single Responsibility Principle represents a way of recognizing classes at the App’s design phase making you think of all the ways a class can change. When you see an excellent clarity of operation requests, a fine separation of responsibilities occurs. Let us comprehend this with an example. public class UserService { EmailService _emailService; DbContext _dbContext; public UserService(EmailService aEmailService, DbContext aDbContext) { _emailService = aEmailService; _dbContext = aDbContext; } public void Register(string email, string password) { if (!_emailService.ValidateEmail(email)) throw new ValidationException("Email is not an email"); var user = new User(email, password); _dbContext.Save(user); emailService.SendEmail(new MailMessage("myname@mydomain.com", email) {Subject="Hi. How are you!"}); } } public class EmailService { SmtpClient _smtpClient; public EmailService(SmtpClient aSmtpClient) { _smtpClient = aSmtpClient; } public bool virtual ValidateEmail(string email) { return email.Contains("@"); } public bool SendEmail(MailMessage message) { _smtpClient.Send(message); } } Read More: Detailed Overview Of Design Patterns In C-sharp Open-Closed Principle (OCP): The Open/closed principle says "A software module/class is opened for the extension and closed for editing". Here, "Open for extension" means that we should design our module/class so that the new feature can only be added when new requirements are generated. Here, "Open for extension" means that we should design our module/class so that the new functionality can only be added when new requirements are created. We should never tamper with it until we find some bugs. As mentioned, a class should be open for extensions, we can use inheritance for this. OK, let's take one example. public class Rectangle: Shape { public double Height {get;set;} public double Width {get;set;} public override double Area() { return Height * Width; } } public class Circle: Shape { public double Radius {get;set;} public override double Area() { return Radius * Radius * Math.PI; } } public class AreaCalculator { public double TotalArea(Shape[] arrShapes) { double area=0; foreach(var objShape in arrShapes) { area += objShape.Area(); } return area; } } Now our code tracks SRP and OCP at once. When you introduce a new form deriving from the abstract class "Shape", you do not need to change the class "AreaCalculator". Liskov Substitution Principle (LSP): The Liskov Substitution Principle (LSP) states that "you should be able to use any derived class instead of a parent class and have it behave in the same manner without modification". It guarantees that a derived class does not affect the behavior of the parent class, in other words, that a derived class must be substitutable for its basic class. This principle is just an extension of the Open-Closed Principle and it means that we need to make sure that the new derived classes extend the basic classes without changing their behavior. I will explain it by employing a concrete example that violates the LSP. A dad is a doctor and his son wants to be a cricket player. So here the son cannot replace his father even if the two belong to the same family hierarchy. public class SqlFileManager { public string GetTextFromFiles(List aLstReadableFiles) { StringBuilder objStrBuilder = new StringBuilder(); foreach(var objFile in aLstReadableFiles) { objStrBuilder.Append(objFile.LoadText()); } return objStrBuilder.ToString(); } public void SaveTextIntoFiles(List aLstWritableFiles) { foreach(var objFile in aLstWritableFiles) { objFile.SaveText(); } } } Looking for Trusted C# Development Company ? For Your Business? CONNECT US Interface Segregation Principle (ISP): The principle of segregation of interfaces stipulates that customers should not be forced to implement interfaces they do not use. Rather than a fat interface, many small interfaces are preferred depending on method groups, each serving as a sub-module. It may be defined differently. An interface should be closer to the code which uses it than to the code which implements it. So the methods of the interface are defined by the methods that the client code needs instead of the methods that the class implements. As a result, clients should not be forced to depend on interfaces they do not use. As with classes, each interface needs to have a specific purpose/responsibility (see SRP). You should not have to implement an interface when your object does not share this goal. The larger the interface, the more likely it is that it includes methods that not every implementer can do. That's the essence of the Interface Segregation Principle. Let's start with an example. public class TeamLead: IProgrammer, ILead { public void AssignTask() { //Code to assign a Task } public void CreateSubTask() { //Code to create a sub-task from a task. } public void WorkOnTask() { //code to implement to work on the Task. } } Dependency Inversion Principle (DIP): The Dependency Inversion Principle says that all the high-level modules and classes must not depend on low-level modules and classes. All must depend upon abstractions. Also, abstractions must not depend upon details of it, instead, they should depend upon abstractions. public class DataExporter { public void ExportDataFromFile() { ExceptionLogger _exceptionLogger; try { //code to export data from files to database. } catch(IOException ex) { _exceptionLogger = new ExceptionLogger(new DbLogger()); _exceptionLogger.LogException(ex); } catch(SqlException ex) { _exceptionLogger = new ExceptionLogger(new EventLogger()); _exceptionLogger.LogException(ex); } catch(Exception ex) { _exceptionLogger = new ExceptionLogger(new FileLogger()); _exceptionLogger.LogException(ex); } } } Conclusion In this article, we discussed the C# SOLID principles for better coding standards and how to make it as fruitful as possible. We also saw a few examples to deeply understand these concepts.
Build Your Agile Team