Introduction
One thing is certain in all software development, all software changes during its life cycle. So, it is imperative that developers design software that is stable (i.e. does not change) in the face of ever changing functional requirements. To aid in this development, Robert Martin gave us the open-closed principle, which states:
SOFTWARE ENTITIES (CLASSES, MODULES, FUNCTIONS, ETC.) SHOULD BE OPEN FOR EXTENSION, BUT CLOSED FOR MODIFICATION [Martin].
Software modules that satisfy the open-closed principle have two huge benefits. First, they are "open for extension". (The behavior of the modules can be altered to satisfy the changing requirements.) Second, these modules are "closed for modification". (The modules, themselves are not allowed to change.) In this article, we will describe the open closed principle via a simple example.
Background
Consider the following example. We have an application that must be able to manage different kind of shapes (i.e. circles, squares, polygon, etc.) and draw them on a standard GUI. Using techniques that do not conform to the open-closed principle, we might solve this problem as shown below:
class Shape
{
private Point _center;
#region Center Get / Set
};
class Circle : Shape
{
private int _radius;
#region Radius Get / Set
}
class Square : Shape
{
private int _side;
#region Side Get / Set
}
These first classes encapsulate the shapes' data structure. The following manager performs several operations on the shapes such as draw
and serialize
.
class DrawManager
{
private List<Shape> _shapeList;
public DrawManager()
public void add(Shape s)
public void serializeShape(string fileName)
public void drawShapes()
{
foreach(Shape s in _shapeList)
{
if (s is Circle)
{
_drawCirle((s as Circle));
}
else if (s is Square)
{
_drawSquare((s as Square));
}
}
}
private void _drawCirle(Circle c)
private void _drawSquare(Square sq)
private void _serializeCirle(Circle c)
private void _serializeSquare(Square sq)
};
The method drawShapes
walks a list of Shape
, examining the Shape
type and then calling the appropriate private draw
method (either _drawCircle
or _drawSquare
). The problem with the above design is that it violates the Open Closed Principle because it cannot be closed against new kinds of shapes. If I wanted to extend this function to be able to draw a list of shapes that included triangles, I would have to modify the function. In fact, I would have to modify the function for any new type of shape that I needed.
Of course this program is only a simple example. In real life, the switch
statement in the drawShapes
function would be repeated over and over again in various functions all over the application; each one doing something a little different (for example, the method serializeShape
has the same problem). Adding a new shape to such an application means hunting for every place that such switch
statements (or if
/else
chains) exist, and adding the new shape to each. Moreover, it is very unlikely that all the switch
statements and if
/else
chains would be as nicely structured as the one in drawShapes
. It is much more likely that the predicates of the if
statements would be combined with logical operators, or that the case
clauses of the switch
statements would be combined so as to “simplify” the local decision making. Thus the problem of finding and understanding all the places where the new shape needs to be added can be non-trivial.
Solution
The following code is a possible solution to the shapes problem that it conforms to the open-closed principle. In this case, the abstract Shape
class defines a single pure-virtual function called Draw
. Both Circle
and Square
override the function in a different way.
abstract class Shape
{
private Point _center;
public abstract void Draw();
};
class Circle : Shape
{
private int _radius;
public override void Draw()
}
class Square : Shape
{
private int _side;
public override void Draw()
}
class DrawManager
{
public void drawShapes()
{
foreach (Shape s in _shapeList)
s.Draw();
}
}
Note that if we want to extend the behavior of the DrawShapes
function to draw a new kind of shape, now, all we need to do is add a new derivative of the Shape
class. The DrawShapes
function does not need to change. Thus DrawShapes
conforms to the open-closed principle. Its behavior can be extended without modifying it.
In the real world, the Shape
class would have many more methods. Yet adding a new shape to the application is still quite simple since all that is required is to create the new derivative and implement all its functions. There is no need to hunt through all of the application looking for places that require changes. Since programs that conform to the open-closed principle are changed by adding new code, rather than by changing existing code, they do not experience the cascade of changes exhibited by non-conforming programs.
Points of Interest
As you could realize, the Open Closed Principle is referring to using Abstract
Classes and their Concrete SubClasses to extend behavior. This essentially means using something like the Template Method Pattern or Strategy Pattern to delegate either part or all of an algorithm in a Concrete SubClass [Gof]. A Template Method is a Method in an (abstract
) base class which calls one or more HookMethods
to fulfill parts of its tasks. You can easily extend or change the behavior of the abstract
base class by creating new subclasses that implement a new version of the Hook
method. This fulfills the requirements of the Open-Closed Principle.
Conclusion
It should be clear that no significant program can be 100% closed. For example, consider what would happen to the DrawShapes
function if we decided that all Circles
should be drawn before any Squares
. The DrawShapes
function is not closed against a change like this. In general, no matter how “closed” a module is, there will always be some kind of change against which it is not closed. Since closure cannot be complete, it must be strategic. That is, the designer must choose the kinds of changes against which to close his design. This takes a certain amount of prescience derived from experience. The experienced designer knows the users and the industry well enough to judge the probability of different kinds of changes. He then makes sure that the open-closed principle is invoked for the most probable changes.
References
History
- June 2006: Initial version
Simone Viganò is from Milano, Italy. He has been in the programming career since 1999, when he was 20 years old.
He has filled a variety of roles ranging from Junior Developer to Senior Software Engineer and Consultant.
For the last 5 years he has worked extensively on Artificial Vision System programming VC++/MFC, ATL/COM and DirectShow.
Simone is also experienced with C#, VB.net and Java (J2EE - J2ME).
Actually He is working as a Team Leader for a company involved in the building automation systems (HVAC-BAS).
He has his Degree in Computer Science from Milano-Bicocca University, Italy.
He has a Master in ICT (Information Technology and Comunication) Management
from Milano-Bicocca University, Italy.
Simone is just a passionate cool-dude about Software Enginee.