Let's state the principle: "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification" (see https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle).
Here are my personal thoughts on how to achieve that.
Let's consider a "set-theoretical" approach. If we view a class as a set $A$ of lines of code. Then we must not modify the content of $A$. This represents the closure aspect.
However, to extend the functionality of $A$, we will need to modify certain parts of $A$.
Therefore, we must (1) extract those parts from $A$ as external sets, such as $B$, $C$, and so on.
Next, (2) establish relationships $R_1$, $R_2$ ... between the parts of $A$ where code was extracted and the new sets $B$ and $C$.
At this point, we have two options:
- Extending the functionality by modifying $B$, $C$, etc., would contradict the Open-Closed Principle for $B$, $C$, etc.
- Extending the functionality by introducing new sets (classes) such as $B'$, $C'$, etc., and defining relations $R_1$, $R_2$, etc., in a manner that allows seamless switching between $B$ and $B'$, $C$ and $C'$, etc.
I think option 2 aligns closely with the concept of "decoupling".
Mathematically, to enable the flexibility to switch between $B$ and $B'$, $C$ and $C'$, etc., the relations $R_1$, $R_2$, etc.,
must establish links between set $A$ and families of sets ${B, B', B'', ...}$, ${C, C', C'', ...}$, and so forth. Like set-valued mappings.
For example, the relation "use an object b of extendable class $B"$ is a solution. Because if we have inside $A$ the line
where b is of type B, then we can always replace b with an object of type B', where B' extends B.
A more explicit relationship might be described as "use an object b of interface B" or "use an object b of abstract class B". In light of the above, an interface serves as a relationship to a family or hierarchy of interfaces and classes that implement it.
A specific type of relation is the mapping, or function. If we put inside A the line
To extend the functionality of method1(), it must relate not only to its own body but also to a family of methods named method1(). This can be achieved using the template method pattern.
Possibly, the last part involving the mapping concept could be refined to "use a method that can be overridden".
Some links:
https://www.cs.utexas.edu/users/downing/papers/OCP-1996.pdf