“Software entities should be open for extension,
but closed for modification.”
To be open to extension, but closed to modifications means that software entities should be flexible enough to allow polymorphic behavior without change their stable interfaces. That also means that any binary should remain as is, but open enough to allow extensions, new functionality, without recompiling.
We accomplish that mainly with the application of DIP, or the “programming to interface, not implementations” principle. But also following the “plugin” model, where the main part of the code doesn’t know about its helpers.
The trivial example below is a common one:
Instead of a set of concrete classes differentiated by type (the classes above the line), we should create a stable Payment part (abstract class or interface IPayment) and inherits or implements the polymorphic operations Pay() in the concrete classes or subtypes. Any client of Payment could incorporate any new pay behavior without any changes, only extending the capabilities instantiating the concrete new payment object.
Another common solution to implement OCP is to use the Plugin Gof design pattern, creating multiple subsystems or micro-services to solves different technical operations (logging, persistence, etc.). With plugins, the core system includes only the concrete implementations at desire, allowing multiple and variable behavior without recompiling. The abstract type that protect the plugin will encapsulate the real instance the application needs under a uniform, standard interface.
Check also the Decorator GoF design pattern for examples at a more detailed scale of adding new functionality without changing the existing code. With a decorator, new functionality could be added as extensions to the existing objects.
The Template Method GoF design pattern is another more granular solution to sustain the OCP. It shows how to apply polymorphic behavior under a single method. Using template methods, the parent abstract class delegates behavior and implementation details to sub-types. No matter the future specification changes, the template method is the layout of a stable algorithm, which details can be developed for a new implementation.