“The Open/Closed Principle states that the design and writing of the code should be done in a way that new functionality should be added with minimum changes in the existing code. The design should be done in a way to allow the adding of new functionality as new classes, keeping as much as possible existing code unchanged.”
The picture on the right illustrates the application of this principle to an object we know very well: Professional cameras can be attached to several types of lenses. The camera itself does not need to be changed, it is simply extended. It is “closed” but also “open”.
The Open/Closed principle was originally proposed by Bertrand Meyer: “Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.”
A simple example of the Open/Closed principle is the Strategy design pattern. In this pattern, the Strategy is a component, and the same composite class may be associated to several types of strategies. The composite class using the strategy never needs to be changed to use a new type of strategy, and in this sense it is “closed”. But it is also “open”, because its behavior can be
extended through associations to new concrete strategies.
It is easy to understand the meaning of the Open/Closed principle in the context of relationships between pairs of classes, such as inheritance and composition. It is also relatively simple to use it in low-level design, and actually many design patterns apply this principle. But it is interesting to ask ourselves how the Open/Closed principle may affect the high-level design.
The Open/Closed Principle Applied to High-Level Design
The approach of Adaptable Design Up Front (ADUF) is an application of the Open/Closed principle to high-level design. It focuses on the early definition of the “closed” aspects of the design while at the same time allows easy evolution by making this design open to extensions.
The design is “closed” when it captures the essential aspects of the system: The main entities and relationships. These are the aspects that are not expected to change.
The design is “open” when it can be easily extended without changes. Through these extensions, the closed part of the design can be adapted to changing requirements.
ADUF starts with a domain model defining the main entities and relationships. We then design a framework for this model, in which the relationships among entities are implemented as associations among classes.
The associations must be closed: An object of type A must have a reference to an object of type B. But polymorphism allows this association to be also open: An object of type A can be associated to an instance of a subclass of B, even subclasses that did not exist when A was created.
ADUF also promotes the usage of Plug-ins as an application of the Open/Closed principle to high-level design. The same module may be associated to diverse plug-ins without any need of change. The module associated to the plug-ins may appear to be completely opaque and monolithic from the point-of-view of the plug-in developer.
Enough Design Up Front
Question: How much design should be done up-front?
Answer: The design up-front should capture all the “closed” aspects of the system. Besides that, we should invest to make this design “open”, in order to allow it to evolve through specialization. This may be done with appropriate Design Patterns.
Thus, there are several kinds of mistakes when doing the design up-front:
- Too much design up front: Capturing aspects that are not essential as if they were such. The consequence is that the design may need to change, and if it needs to change then it is not really “closed”. This also happens when specific details are not generalized. If the details change, the design must be changed.
- Not enough design up front: Leaving away some of the essential aspects of the system. The consequence is that these aspects will need to be added to the design when other parts of the system are already implemented. If the original design was not “open” enough, the new additions may cause conflicts.
- Not enough openness: The design captures all essential aspects, but there are not mechanisms that allow this design to be easily extended. For example, if there are Interfaces or Abstract classes that represent the main entities, there should be also creational patterns such as Factories to instantiate specific subclasses.
- Too much openness: The design is over engineered, resulting in complexity not really necessary. For example, it is always possible to reduce the coupling between modules through additional layers of indirection. However, if there are many such layers, it becomes more difficult to locate the places in the code where the business logic is actually implemented.
According to the ADUF approach, the design up front should be:
- Closed: Capturing the essential aspects that are not going to change.
- Open: Allowing the initial design to be easily extended and adapted.
What do you think? Have you used similar approaches? Please share your experience in the comments below.