- Home>
- Design Patterns>
- The Open Closed Principle
In this post, I continue to share what I have learned about the SOLID principles in the book “Clean Architecture A Craftsman’s Guide to Software Structure and Design”. As a recap, six principles make up the SOLID acronym:
In the previous post, I wrote about the Single Responsibility Principle. In this post, I write about the Open Closed Principle.
The Open Closed Principle states that a module should be open for extension, but closed for modification. The definition can be confusing, which is why I suspect the author does not even mention about it in the book. The principle does not mean that you cannot change the source code of a module to extend its behavior. Rather, the idea is that you should design your modules and arrange the dependencies between them such that when you modify the implementation of a module, you don’t have to modify other modules that depend on it.
Modern languages and frameworks today have built in features that helps with following the Open Closed Principle. For instance, you can use interfaces, generics, lambda expressions etc … which are available in languages such as C#, Java, and TypeScript to help make your modules less dependent on concrete types so that when changes occur in the concretes, you don’t have to update your modules. One example is using C# extension methods to extend the behavior of an existing class without having to modify the source codes in the first place. Another example is using Entity Framework to shield your modules against certain changes to the database. Instead of using database dependent queries, Entity Framework enables you to work with objects and TSQL such that you don’t have to change much codes if you switch to a different database. Although the languages and frameworks we use can have great support for adhering to the Open Closed Principle, it’s ultimately up to us to ensure we do not violate the principle. How can we do that?
The author outlines the strategy to follow the Open Closed Principle. First, we need to separate the modules that change for different reasons. This is the idea of the Single Responsibility Principle. Then, should a higher level module depends on a lower level module, it should only depend on the interface of the lower level module, not the implementation. That way, we can protect the higher level module from changes in the lower level module. This come back to the idea of programming to an interface whenever possible.
As an example, consider the diagram which I derived from one of the applications I work on. In the diagram, the main operations are saving and retrieving a user, which are two separate responsibilities. For example, suppose the user is an external user with a social account. Saving the user means storing the user’s social id, whereas retrieving the user entails getting the id from the database and calling the social site to retrieve the user’s data. Following the Single Responsibility Pattern, I abstract each operation into its own class. This way, when I need to change one of the operations, I avoid accidentally change the codes in the other and introducing regression bugs. Furthermore, I protect the controller from changes in the lower level classes, by not having the controller to depend directly on the implementations of those classes. As such, the design adheres to the Open Closed Principle. In fact, the design also complies with the Dependency Inversion Principle because the interfaces decouple the higher level classes from the lower level classes. Other patterns you may notice in the design is the CQRS pattern and Mediator pattern. I probably cover these in other posts.
Clean Architecture: A Craftsman’s Guide to Software Structure and Design (Robert C. Martin Series)
Notes on the three programming paradigms
Notes on The Dependency Inversion Principle
Common frameworks, libraries and design patterns I use
Notes on component coupling
Notes on Component Cohesion
Notes on The Clean Architecture
Notes on the Interface Segregation Pattern
Notes on Barbara Liskov paper on data abstraction and hierarchy