Understanding SOLID Principles: Open-Closed
19 august 2020
That is the second article from the series about SOLID principles. This time I should give you more details about the Open-Closed Principle. The element of software should be locked for modification but open to extension. Uncle Bob himself used to say: “Good architecture reduces the amount of modified code to the absolute minimum. Ideally to zero”.
Major errors made during system expansion
Thanks to SRP we know how to divide the system into smaller parts that can be easily extended. Easily? But how? Let’s imagine that Payroll Department wants to extend the PayCalculator class mentioned in the previous article by implementing other methods of calculation. The basic form of calculation was hour-based calculation. The next step was adding the bonus calculation for sales department employees. In the next stage calculation for drivers and B2B co-workers was added changing at the same time the name of original method into ‘fullTimepay()’.
Is such a development of the system correct? According to SOLIDS’s first principle everything seems to be well-defined – our class has only one actor – Payroll Department, and therefore – one source of changes. It turns out however that the complexity of choosing the right method of wages calculation more and more increases cyclomatic complexity. This method results in the necessity of making modifications in the class with every single change introduced. And what’s worse, there will be a need for “external” changes for other modules/classes to be able to use new methods.
According to Robert C. Martin’s recommendations the number of changes in the class and related code elements are far too high.
Looking at the tasks that are carried out by the class it can be easily noticed that its main purpose is to calculate the wages of the employees by means of different algorithms. It is worth to pay attention to the fact that each algorithm is independent from others and there is a high probability of the occurrence of another algorithm (but it doesn’t really mean that such one must appear).
We use inheritance and polymorphism in such a scenario. While creating the frame of the class – the abstract class for instance – and its further implementations, we’ll obtain the following class scheme:
The above scheme allows to achieve a very important feature. If we use the ‘factory model’ in the process of creating new classes inherited from PayCalculator it will become the only place in the system where the changes, expanding the system and its new functions, can be implemented. The classes related to payroll calculations will be blocked for any modifications and open to extensions. Furthermore, the system can be extended also with the classes that will not inherit straight from PayCalculator.
A bit more about architecture
While using the OCP in designing the payroll calculation we will have to create certain abstraction, a base class. It will be fixed and invariable during the whole life cycle of the system. There might be many different implementations but the said class would remain invariable.
The other modules in the system will depend on that invariable abstract. It will positively affect the stability and reliability of the system. It will be easier for testing (e.g. unit testing), and the need of introducing the changes will not be a threat to anyone.
The essence of OCP is to use the simplicity of its steering components whilst making the class expansion. Using the polymorphism and virtual methods, programming increases the functionality of the systems without the negative impact on the clarity and integrity of the classes. It all involves the use of best patterns and practices. It is worth mentioning that this process affects positively the whole architecture of the system, which becomes more resistant to changes, whilst the modules become more independent. And the cyclomatic complexity decreases.