In the previous two posts I discussed the S of S.O.L.I.D. which is the Single Responsibility Principle (SRP) and the D of S.O.L.I.D. which corresponds to the Dependency Inversion Principle (DI). This time I want to continue this series with the second letter of S.O.L.I.D. namely the O which represents the Open Close Principle (OCP).
In object-oriented programming the open/closed principle states
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
That is such an entity can allow its behavior to be altered without altering its source code.
The first person who mentioned this principle was Bertrand Meyer. He used inheritance to solve the apparent dilemma of the principle. His idea was that once completed, the implementation of a class could only be modified to correct errors, but new or changed features would require that a different class be created.
In contrast to Meyer’s definition there is a second way to adhere to the principle. It’s called the polymorphic open/close principle and refers to the use of abstract interfaces, where the implementation can be changed and multiple implementations could be created and polymorphically substitute for each other. A class is open for extension when it does not depend directly on concrete implementations. Instead it depends on abstract base classes or interfaces and remains agnostic about how the dependencies are implemented at runtime.
OCP is about arranging encapsulation in such a way that it’s effective, yet open enough to be extensible. This is a compromise, i.e. “expose only the moving parts that need to change, hide everything else”
There are several ways to extend a class:
- proxy implementation (special case for composition)
Is a sealed class contradicting the OCP? What one needs to consider when facing a sealed class is whether one can extend its behavior in any other way than inheritance? If one injects all dependencies, and those are extendable, commonly they’re interfaces then the sealed class can very well be open for extension. One can plug in new behavior by swapping out collaborators/dependencies.
What about inheritance?
The ability to subclass provides an additional seam to accomplish that extension by putting the abstraction not to codified interfaces but to the concept captured in overridable behavior.
Basically you can think of it a bit like this, given a class that have virtual methods if you put all of those into an interface and depended upon that you’d have an equivalent openness to extension but through another mechanism. Template method in the first case and delegation in the second.
Since inheritance gives a much stronger coupling than delegation the puritan view is to delegate when you can and inherit when you must. That often leads to composable systems and overall realizes more opportunities for reuse. Inheritance based extension is somewhat easier to grasp and more common since it’s what is usually thought.
As stated above the OCP can be followed when a class does not depend on the concrete implementation of dependencies but rather on their abstraction. As a simple sample consider an authentication service that references a logging service to log who is trying to be authenticated and whether the authentications has succeeded or not.
Since the the authentication service only depends on an (abstract) interface ILogger of the logging service
and not on a concrete implementation the behavior of the component can be altered without changing the code of the authentication service. Instead of logging to e.g. a text file which might be the default we can implement another logging service that logs to the event log or to the database. The new logger service has to just implement the interface ILogger. At runtime we can inject a different implementation of the logger service into the authentication service, e.g.
var authService = new AuthenticationService();
authService.Logger = new DatabaseLogger();
There are some good examples publicly available of how a project can adhere to the OCP by using composition. One of my favorites is the OSS project Fluent NHibernate. As an example there the auto-mapping can be modified and/or enhanced without changing the source code of the project. Let’s have a look at the usage of the AutoPersistenceModel class for illustration.
Without changing the source code of the AutoPersistenceModel class we can change the behavior of the auto mapping process significantly. In this case we have (with the aid of some lambda expression magic –> see this post) changed some of the conventions used when auto-mapping the entities to database tables. We have declared that the name of the database tables should always be the same as the name of the corresponding entity and that the primary key of each table should have the name of the corresponding entity with a postfix “Id”. Finally the version column of each table should be named “Version”.
This modification of the (runtime) behavior is possible since the AutoPersistenceModel class depends on abstractions – in this case lambda expressions – and not on specific implementations. The signature of the WithConvention method is as follows
public AutoPersistenceModel WithConvention(Action<Conventions> conventionAction)
Let’s assume we want to implement a little paint application which can draw different shapes in a window. At first we start with only one single kind of graphical shape namely lines. A line might me defined as follows
It has a draw method which expects a canvas as parameter.
Now our paint application might contain a Painter class which is responsible to manage all line objects and which contains a method DrawAll which draws all lines on a canvas.
This application has been in use for a while. Now all of the sudden the users does not only want to paint lines but also rectangles. A naive approach would now be to first implement a new class Rectangle class similar to the line class which also has a Draw method
and then modify the Painter class to account for the fact that now we also have to manage and paint rectangles
One can easily see that the Painter class is certainly not adhering to the open/closed principle. To be able to also manage and paint the rectangles we had to change its source code. As such the Painter class was not closed for modifications.
Now we can easily fix this problem by using inheritance. We just define a base class Shape from which all other concrete shapes (e.g. lines and rectangles) inherit. The Painter class then only deals with shapes. Let’s first define the (abstract) Shape class
All concrete shapes have to inherit from this class. Thus we have to modify the Line and the Rectangle class like this (note the override keyword on the draw method)
Finally we we modify the Painter such as that it only references shapes and not lines or rectangles
If ever the user wants to extend the paint application and have other shapes like ellipses or Bezier curves the Painter class (and especially the DrawAll method) has not to be changed any more. Still the Painter can draw ellipses or Bezier curves since these new shapes will have to inherit from the (abstract) base class Shape. The Painter class is now closed for modification but open for extension since we can add other kinds of shapes.
I’d like to see OCP done in conjunction with “prefer composition over inheritance” and as such, I prefer classes that have no virtual methods and are possibly sealed, but depend on abstractions for their extension. I consider this to be the “ideal” way to do OCP, as you’ve enforced the “C” and provided the “O” simultaneously. Of course, please don’t construe this as meaning that there is no way to use inheritance properly or that inheritance somehow violates OCP. It doesn’t. It’s just that because of the way most of us learned OOP, we tend to think of inheritance first, when people talk about “extension of an object” .
Post Footer automatically generated by Add Post Footer Plugin for wordpress.