Welcome to part two of the SOLID OOP design pattern series. You may wish to subscribe to the blog to stay tuned in as I post new issues in this series.
SOLID is not a language, it’s not a framework, it’s a set of principles to help guide your team to design better applications. Using these principles will help ensure your code is adaptive to change. In this series, I’ll be using C# for examples, but the same principles apply to any OOP language: Python, Java, C++, etc.. Using SOLID design principles require a fundamental understanding of classes, interfaces, and class inheritance.
The second letter in SOLID stands for OCP: Open/Closed Principle. This principle, defined in Bertrand Meyer’s 1988 book, Object-Oriented Software Construction, might first appear to be an oxymoron like Advanced BASIC, Microsoft Works, or American Soccer, but the definition is simple: “Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.” Let’s break it down.
You should write classes so they can be extended through inheritance, allowing for requirements to change while locking in current functionality. One way we can accomplish this is to write interfaces and abstract classes, in Object Oriented Programming this is known as polymorphism. Covering polymorphic strategies exceeds the limits of a single post, but what you should take away is that your base interfaces and classes should be abstract enough to allow inherited classes to override functionality. Confused yet? Let’s look at a simple concrete example.
Payroll loved our changes from our last SRP post and the software runs smoothly now. They’d like a new feature to help calculate each employee’s current pay. We quickly whip up a calculate method in our HRM software:
No doubt Payroll loves the change but they soon notice contractor pay wasn’t taken into account. After discussions, we learn employees can be full-time or contractors. Our full-time employees vary in pay rates, but our contractors are set at a pay rate of $12/hr. Since we didn’t leave our code open for extension, we only have one choice: we must modify our existing class:
New Functionality Added to Employee.cs
Writing our class this way forces us to change our existing code each and every time Payroll requests a new pay requirement. If we know requirements won’t change, SOLID would fall under YAGNI (You Aint Gonna Need It).. but If we anticipate requirements changing then we’ll need to design maintainable code.
So what’s the SOLID way? How can we use class inheritance and code abstraction to ensure we don’t change and, therefore, break existing code?. There are many ways to do this, for this example we’ll build a simple Pay interface and two inheriting classes, FTEPay and ContractorPay::
FTEPay and ContractorPay both inherited from the Pay interface and execute different code for their CalculatePay method. This abstractions allows us to move the logic out of our Employee class and use an interface instead. Let’s take a look:
SOLID Employee Class
Our class now contains no logic for the CalculatePay method, we’ve obfuscated the code meaning we can interchange or “plug-and-play” any class inheriting from our Pay interface. We are free to add new requirements from Payroll and that’s great because they’ve asked for a new pay type for employees in California because of new min wage laws exceeding our 12/hr contractor rate. In a few simple steps we can:
- Add a new class (CaliforniaContractor: IPay)
- Write the CalculatePay logic
- Set the Employee.PayType = CaliforniaContractor for contractors in California
We successfully added new functionality, without touching a single line of existing code 🙂
Next week we’ll continue our SOLID principles building on what we’ve learned today with LSP: Liskov Substitution Principle. See you then!