Yes, you read that right. These principles are so important that they are sometimes required by companies but, what do they mean? Solid principles are patterns, or ways of doing things, which you can use when you’re programming to make your code more robust and easier to maintain over time.
What are SOLID principles for?
You might be familiar with the Single Responsibility Principle (SRP) and the Open/Closed Principle (OCP), but there are also four other solid principles that you can use to improve your code and make it more reliable. In this article, we’ll look at all five solid principles and how they can help you build better software and, one last principle that is not exactly part of SOLID but is also important and how to apply it.
It’s been close to two decades since it was first proposed that a set of tenets or guidelines would aid in simplifying OOP design and developing. Today, many people know these five tenets by heart when it comes to programming in object-oriented languages. Whether they work alone or as part of a team, using these principles has helped make both coding solo easier and collaborative coding more successful. Employers will always prefer candidates who understand the basic concepts behind SOLID design principles because their programs are simply more scalable than others’.
Why follow SOLID principles in C#?
Software developers know how hard it is to build something new and make it perfect. There are always unforeseen consequences, problems arising from the initial design, etc. One issue in particular stands out; because we have put so much work into one product, we don’t have time for others. What if this product doesn’t do well at all? How will that affect our other products? And every modification or update requires a deep understanding of the entire system we built — this prevents us from doing anything quickly and efficiently.
Taking care of the design sounds straightforward enough; but it isn’t always so easy. For example, it can sometimes be difficult to make sure that every part of a system is designed in such a way that it’s clean and scalable — this is where SOLID Design Principles come into play. They help developers find hidden complexities and ensure they’re building the right thing before moving forward.
Single Responsibility Principle
The first SOLID principle is known as SRP, or Single Responsibility Principle. According to Martin Fowler’s book, Refactoring: Improving the Design of Existing Code; The basic idea is that every class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class. In plain English, it means each class should do one thing and one thing only. Let’s see Thang Chung example:
I would like to use the example of a utility knife to cut a steak. Is it possible? Yes, it can. Is it the right thing to do? No. It would be better to use a good knife whose only function is to cut the steak. Here in the code the same thing happens.
The Open/Closed Principle (OCP) states that a class should be open for extension but closed for modification. It means that you can’t modify a class directly to add new functionality, but you can create new classes derived from your original class to add new functionality.
This is usually achieved through inheritance, although there are other ways of implementing OCP.
It’s one of those principles where it’s best if you understand its meaning by thinking about what it doesn’t mean rather than what it does. So let me explain in a very simple way how not to apply OCP: Don’t make methods virtual just so you can replace them with concrete implementations later.
Liskov Substitution Principle
Inherit from an interface not a concrete class. This principle states that inheritance hierarchies should be composed exclusively of interfaces and abstract classes, never concrete classes (e.g., if class B inherits from A and C both, but not D, then D violates LSP). In other words,
is-a relationships are stronger than
has-an relationships. Let’s check the example:
That is, inheritance chains should be directed acyclic graphs rather than trees; there should be no cycles. Also note that inheritance is not used for code reuse here: inheritance is only used to ensure semantic consistency across distinct types. Code reuse (i.e., code sharing) is achieved by means of interfaces.
Interface Segregation Principle
Every client should be able to use a class by itself, without needing any other classes. If a client needs another class to properly use one of your classes, there’s a problem. Most developers don’t know it but there is a rule that exists in OOP called the Interface Segregation Principle which states: no client should be forced to depend on methods it does not use. As Thang Chung explains in his example:
In other words, each interface should do only one thing and it should be clear what that one thing is. When we have an interface that contains methods outside of its responsibility, we violate Interfacial Segregation Principle (ISP). To solve these problems with interfaces there are many techniques such as parameterized interfaces and partial classes.
Dependency Inversion Principle
The last of the 5 SOLID principles is called Dependency Inversion Principle (DIP) which was written by Robert C. Martin (commonly known as Uncle Bob). Robert defines the DIP principle as:
It states that high-level modules should not depend on low-level modules. And both should depend on abstractions.
One reason for DIP is to achieve a separation of concerns, where each module only focuses on one area of concern and does it well without unnecessary dependencies spreading throughout your codebase.
- Abstractions should not depend upon details.
- Details should depend upon abstractions.
Don’t Repeat Yourself
This principle, although as I said at the beginning, it is not really part of the 5 SOLID principles, is a cornerstone of clean coding. It states that every piece of knowledge must have a single, unambiguous, authoritative representation within a system. If you find yourself copying and pasting code or using global variables, you’re doing it wrong. DRY code will help you minimize bugs and errors and make your code more flexible and easy to manage. Let’s see Thang Chung example:
Use classes, interfaces and methods when appropriate to ensure that each part of your code only appears once. Think of every class or method as an API: If there’s any ambiguity as to how it should be used, you’re doing it wrong.