Check out the previous part if you missed Single Responsibility Principle.
Why it is important to be open for extension? Change is the keyword in software development and software components are inserted in new projects and environments every day. What makes them useful is the ability for a developer to write adapter and subclasses to get a job done without reinventing the wheel, but only by smoothing and tuning it.
Why it is important to be closed for modification? Because when a closed unit is fully tested and deployed, if it's not modified it can't break. This is a simple consequence of not changing what already works: it will continue to work.
These are some examples of patterns and techniques that help you follow the OCP:
- programming to an interface, not an implementation: a oo interface is closed for modification, and multiple implementations can take new behavior and possibilities into the software system;
- Template Method: some empty methods are called during an algorithm execution to allow overriding by a subclass, providing it hooks in the code flow;
- Iterator Pattern: abstracts away the mechanics of an iteration to let other iterators substitute it in particular conditions.
The strategy of extending behavior without cluttering a base unit is one of object oriented pillars: subclassing, decorators and helpers are only strategies to keep responsibilities out of the base class, which can quickly became a God one if too much code fills it.
Let's take the Car example from part 1 and see if we are violating OCP: a Car is composed by an Engine, a Trasmission and the Brakes. What if we need to move the Car without using a gas or diesel engine? We can design a ElectricEngine subclass which will substitute the former Engine without breaking its contract. The same can be done with CarboniumBrakes or BremboBrakes.
What if the Engine has a method called injectGas()? This violates encapsulation and affects OCP also. An electric engine would not use gas as a combustible and thus the contract is not closed for modification: the problem is in the abstraction of an Engine which is in reality an abstraction for a gas engine. What can be done it's decoupling the Engine with an interface Propulsor which will contain a GasEngineElectronicBoard which translates to the Engine the commands from the driver.
Now if we want to put in an ElectricEngine, we will provide a Propulsor instance which governs the ElectricEngine in some way, with electronic or analogic circuits. Take the time to think about possible changes and how they affect your software systems: find a way to add features without have to resort to old classes modification.
Encapsulation is a checkpoint to achieve OCP: the more you keep private and hide from the public view, the less is assumed in the behavior an interface or an abstract base class. This leads you to write implementations which exposes very few methods and can perform work in unthinkable ways: the Propulsor interface can be implemented by a ElectricEngine but also from a ReactionEngine or a SteamEngine or a HyperDriveEngine if you are a science fiction passionate. The only requirement is that it manages to accelerate the Car and choosing this abstraction makes this parts totally interchangeable.
The Propulsor interface is an example of closure: not only it is not editable since clients expects determinate features, but we cannot add methods because they will break the implementors, especially if their code is not under our control.
Summarizing, Open/Closed Principle forces decoupled and extendable code: it's another milestone towards a maintainable and functional object-oriented application.
You may want to subscribe to the feed to be notified of new articles in this series. The picture at the top is the Usb symbol: nowadays every device that uses Usb and provide a proper driver can extend the behavior of a pc without having to open the case: webcams, printers, scanners...
*Rogerio Liesenfeld points out in the comments that the original formulation of this principle comes from Bertrand Meyer.