Back in 2010 I wrote what became my most popular post in this blog: A Good Example of Liskov Substitution Principle, based on a scenario that came up in a project I worked on at the time. I just realized I’ve never blogged about another scenario that came up in the same project as a good example I use for the Open-Closed Principle (OCP), which is described as follows:
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”; that is, such an entity can allow its behaviour to be extended without modifying its source code. – Wikipedia
I use a simple application to illustrate the scenario. This is a desktop application. The main menu has one sub-menu for each module (e.g., Orders and Employees). Each module has its own sub-menu to expose related functionality:
Initially, the menu was all implemented in the same place… the main window:
The “code behind” the main window called out services in the different modules:
Any time a new feature was added to a module, the main window had to be changed. With several features across several modules being written actively, the main window was a big bottleneck: many developers were trying to change it at the same time! Implemented that way, the main window violated OCP: to extend the menu (adding modules or features to them), the main window had to be changed.
In order to make the main window OCP-compliant, and therefore, improving the implementation, here are the changes made to app…
The main menu in the main window was defined, since it’s that window’s responsibility to host a menu. However, it is NOT its responsibility to know what items go in the menu. In other words, the menu is empty:
The code behind the main window that used to call services in the modules was removed, and the classes started to use an AppMenuBuilder class, which returned a list of MenuItems to be added to the menu. Each module was represented by a Module class (e.g.: OrderModule, EmployeeModule…). In the real world application these modules were registered and initialized automatically using an IoC container (but that’s beyond the point of this post).:
The AppMenuBuilder class simply registered IModuleMenuBuilder instances, and it used those to figure out what menu items were needed:
Each implementation of IModuleMenuBuilder was responsible for creating the menu items that the module needed, and what to execute when the menu item was invoked. See the OrderMenuBuilder as an example:
The MenuItemFactory offered an easy way to create the MenuItem and hook it up to the action to be executed:
Again, the real world implementation of that factory was slightly different: it implemented an IMenuItemFactory interface, which got injected into each Module class. But again, that part is beyond the point of this post. The idea behind having the MenuItemFactory was so to make it easy to hook other operations to the menu system-wide. For example, let’s say we wanted to log every action invoked by any option on the menu. Here’s a simple logger:
Here’s how the MenuItemFactory would leverage additional actions:
And once again, in the real world application this was a little more robust, where “things to be added to the menu actions” were registered automatically to the IoC container.
After making those changes, the main window became open for extension, but closed for modification: options could be added to the menu and actions could be associated with them, and the main window didn’t have to be changed.