Tuesday, September 08, 2009

SOLID part 2: Open/Closed Principle

The Open/Closed principle is the second of the SOLID principles which governs object-oriented development, formulated by Uncle Bob in the 90s*.
Check out the previous part if you missed Single Responsibility Principle.

Classes and methods should be open for extension but closed for modification.
The meaning of this principle is that when a requirement is added to your application, you should be able to handle it without modifying old source files (supposing you have one class per file), but only by adding subclasses and new implementations and changing the configuration.
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.
Every good pattern resemble the OCP. The Iterator Pattern has been particularly developed in php: we have Iterator and IteratorAggregate instances which can be swapped in foreach construct; but we also have a bunch of subclasses that extends the core behavior: FilterIterator, CachingIterator, LimitIterator, RecursiveIteratorIterator...
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.


Curtis Cooley said...

Nice! Always like to see more articles on SOLID principles. If you are interested, I have an expanded version of the Car example at http://tinyurl.com/md58ta

Touches on LSP, which I'm sure you'll get to, but also fits in nicely with what you've written here.

Keep up the good work!

Rogerio Liesenfeld said...

You need to check your facts: the Open Closed Principle was formulated by Bertrand Meyer in 1988, not by Robert Martin in the 90's.

Giorgio said...

You're right, this principle was firstly formulated by Bertrand Meyer. The phrase at the top is referred to the entire set of 5 Solid principles: of course also Liskov Substitution Principle originates from Barbara Liskov and not from Martin. :)