
If there is one thing I have learnt during my career in the IT industry, it’s that the industry is a fickle beast. Trends and fashions come and go. Languages fall by the wayside(hey COBOL74!). How often have you read an article declaring a new framework a “game changer”, only to realise that after using it in anger it does a fraction of what a venerable equivalent does in it’s sleep?
In this article I’m going to cover something that has not changed and has not gone out of fashion. It crops up again and again.
If there’s one thing you need to learn and more importantly USE, as a software engineer it is encapulated(see what I did there?) in these 5 principles. But hey, enough of my yakkin’, whaddaya say? Let’s boogie!
The SOLID principles are a set of five design guidelines in object-oriented software development that help engineers create systems that are easy to maintain, scale, and understand. Introduced by Robert C. Martin, these principles aim to reduce “code rot” and make software more robust.
1. Single Responsibility Principle (SRP)
“A class should have one, and only one, reason to change.”
This principle states that a component should perform a single function. When a class handles multiple unrelated tasks, it becomes fragile. A change in one task might accidentally break another. You might be tempted to add a small related function, but don’t do it. Do what is right and create a new class even if it has one function. Smaller classes are great. Less dependencies, easier to test. What’s not to like?
- Example: Imagine a
Userclass that handles both user data and saving that data to a database. If you change your database schema, you have to modify theUserclass. - Better Approach: Create a
Userclass for data and aUserRepositoryclass for database operations.
2. Open/Closed Principle (OCP)
“Software entities should be open for extension, but closed for modification.”
This somewhat opaquely named principle states that you should be able to add new functionality to a system without changing existing code. This prevents bugs from being introduced into parts of the application that are already working. It comes down to my tenet of minimal code change. Remember every code change has possibility to introduce bugs!
- Example: A
Discountclass that uses a series ofif/elsestatements to check for “VIP” or “Seasonal” discounts. Adding a new discount type requires changing the existing logic. - Better Approach: Use an interface or abstract class
DiscountStrategy. Each new discount type becomes a new class that implements this interface.
3. Liskov Substitution Principle (LSP)
“Subtypes must be substitutable for their base types.”
Barbara Liskov is a pioneer who fundamentally changed how we write and organize code. Before her work in the 1970s, code was often a messy “spaghetti” of instructions. Liskov pioneered the concept of Data Abstraction. She led the team that created CLU, a programming language that introduced the idea of “abstract data types”—the direct ancestor of the “Classes” and “Objects” we use in almost every modern language like Java, Python, and C++. I hope you enjoyed that little history lesson. Let’s proceed.
This principle states that if a program is using a base class, it should be able to use any of its subclasses without knowing it or causing errors. The subclass must honor the “contract” of the parent class.
- Example: A classic violation is the “Square-Rectangle” problem. If a
Squareinherits fromRectanglebut throws an error when the height and width are set to different values, it breaks the program’s expectations. - Better Approach: If a subclass cannot perform the actions of the parent in the same way, they likely shouldn’t share that specific inheritance hierarchy.
4. Interface Segregation Principle (ISP)
“Clients should not be forced to depend on methods they do not use.”
I’ve seen this many times! You have to implement an interface in order to use a specific API call. You do this but realise you have to implement functions you are not interested in, leading to the dreaded “not implemented” comment. This can be partly remedied by using the Adapter Pattern by the way if you come across it.
It is better to have many small, specific interfaces than one large, “fat” interface. This prevents implementing classes from being burdened with “dummy” methods that do nothing.
- Example: An
IMachineinterface withPrint(),Scan(), andFax(). A basicPrinterclass would be forced to implementScan()andFax()even if it can’t perform those actions. - Better Approach: Break the interface into
IPrinter,IScanner, andIFax machine.
5. Dependency Inversion Principle (DIP)
“Depend on abstractions, not concretions.”
High-level modules (the logic) should not depend on low-level modules (the tools). Both should depend on abstractions (interfaces). This “decouples” the code, making it easy to swap out components.
This is great for writing tests, and you should be writing tests, many, many tests!! It allows you to easily mock the dependencies.
- Example: A
NotificationServicethat directly creates an instance ofEmailSender. If you want to switch toSMSSender, you have to rewrite theNotificationService. - Better Approach: The
NotificationServiceshould depend on anIMessageSenderinterface. You can then “inject” whichever sender you need at runtime.
Conclusion
At the end of the day, SOLID is about managing change. Requirements shift, APIs evolve, and businesses pivot.
By following these five principles, you aren’t just writing code for today; you’re leaving a map for the developer who has to touch this file six months from now. It turns software from a fragile house of cards into a robust, modular system.
Before I go, here is a test. Write some code. Store it away for a year. Look at your code. Is it still readable, understandable. Is is SOLID?


























