First, a quick recap: the SOLID principles of object oriented design are, to quote Uncle Bob:
The Single Responsibility Principle | A class should have one, and only one, reason to change. |
The Open Closed Principle | You should be able to extend a classes behavior, without modifying it. |
The Liskov Substitution Principle | Derived classes must be substitutable for their base classes. |
The Interface Segregation Principle | Make fine grained interfaces that are client specific. |
The Dependency Inversion Principle | Depend on abstractions, not on concretions. |
Last time I talked about the first letter in SOLID, the Single Responsibility Principle. Now I’m moving on to the Open Closed Principle.
The Open Closed principle says that you should be able to extend a class’s behaviour without modifying that class directly. In other words, the class should be open for extension and closed for modification. Okay cool, but what does that really mean? That you should design your classes in a way that when you need to add new features to your system you can do it by adding new code without messing with existing code that already works. Remember, most of software development is accepting that it’s hard and trying not to completely screw it up. When you change code that already works, you risk breaking everything that depends on it. It’s safer to add new code to a child class (for example) that’s separate from the existing code so you can break your new feature without wrecking everyone else’s day.
Open/closed is about how you arrange your abstractions. The name Open Closed Principle can be kind of confusing, it’s not about somehow preventing other people from changing existing code with stern comments threatening to replace their good chair with the crappy broken one that got abandoned in the conference room, it’s about writing your code so other people (or you in six months) don’t have to change the existing code. It’s the not needing to change your code part that makes your class closed to modification.
For example, suppose you have some classes for employees and contractors, and a report building class that calculates everyone’s pay for the month. If that report building class has an if or case statement that checks whether the current person to calculate pay for is an employee or a contractor and handles them differently, then your class has to be updated every time you add a new type. Maybe you need to handle interns now, or both salaried and hourly employees, or full and part time employees, or sales people who get paid different commissions, or or or. The more employee types you add the bigger and uglier your case statement gets and the more chance you’ll forget one or mess it up.
Instead, you need an interface named PayableIndividual with a method called calculatePay(Date start, Date end). Then your concrete classes like FullTimeEmployee and Contractor can implement that interface. If your report generating class only uses PayableIndividuals, not the concrete classes that implement that interface, you an add all the subtypes you want without ever having to mess with the report generator because all it has to do is call calculatePay and let the concrete class do the work.
The open for extension part of the Open Closed Principle is about keeping processing that’s specific to an individual piece of code separate. It might be tempting to make that calculatePay method I mentioned above a method in an abstract PayableIndividual class and let the subclasses just add what they need to after they call the base calculatePay method. If you really do have a base hourly rate everyone gets paid that could work, but if, say, some contractors get paid by the hour and some sell blocks of work for a fixed price then you’ll have to tear out the base calculatePay method that doesn’t make sense anymore. If it can vary, separate it out so that you can simply override it in a base class without reworking your entire design.
Of course, you can’t have code that perfectly adheres to the open closed principle if it actually does anything useful. Sooner or later you’ll run into a problem that just doesn’t bit perfectly into your nice tidy design. For example, you might want your salary report ordered by employee type and then name. Whatever code does the ordering has to change when you add new employee types – all you can do is keep the mess as contained as possible. If you use a comparator with a table that contains the order all the employee types should be printed in then only that table has to change when you add a new employee type. Not perfect but it could be much worse.
So why is it so bad to modify a base class? It’s a risk. Every time you modify a class, you risk breaking everything that depends on it. That’s bad enough if it’s a class used only by one project, but it’s really scary if it’s a class in a shared library. If you could break every project that uses that library, then every project needs to be retested and that can be massively expensive in terms of both time and money. It can also make other developers hate you, which is exactly what we’re trying to avoid with these principles :)
Next up, the Liskov Substitution principle!