I wrote an answer previously on Open-Closed Principle (OCP) vs Liskov's Substitution Principle (LSP) and both those principles relate to each other a lot, but are still conceptually different with some contrived examples of having one but not the other. Because of this answer I'll only touch on OCP briefly and dwelve deeper into DIP and what makes that tick.
Lets try discussing how OCP relates and differs with Dependency Inversion Principle (DIP) by first explaining the different principles first.
Dependency Inversion Principle
Reading Uncle Bob's Principles of OOD you'll find that DIP states the following:
Depend on abstractions, not on concretions.
An abstraction in Java is simply achieved with interface
and abstract
keywords, meaning you have a "contract" for some software entity that the code has to follow. Some programming languages do not have a facility to explicitly set behaviors for the code to follow so abstractions have to be followed in a more manual fashion rather than having the compiler help you enforce the contract. E.g. in C++ you have classes with virtual methods, and dynamic programming languages such as Javascript you have to make sure you use objects the same way (though in the case of Javascript this has been extended in TypeScript that adds a type system to help you out with writing contracts that is verified by the compiler).
The name includes the term "inversion" because traditionally (y'know in the old dark ages of programming) you wrote software structures that had higher level modules depending on low level modules. E.g. it made sense to have a ButtonAtKitchen
handling inputs for a KitchenLamp1
and KitchenLamp2
. Unfortunately that made software a lot more specific than it needed to be and the object graph would look like this:
So when you make the software more general, by adding "contracts". Notice how the arrows in the object graph "inverts" the direction. That kitchen lamps are now dependent on a Button
. In other words the details are now dependent on abstractions instead of the other way around.
Thus we have a more general definition of DIP, also detailed in the original article of DIP by Uncle Bob.
A. High level modules should not depend on low level modules. Both should depend on abstraction.
B. Abstractions should not depend upon details. Details should depend on abstractions.
Open-Closed Principle
Continued from Uncle Bob's principles you'll find that OCP states the following:
You should be able to extend a classes behavior, without modifying it.
One example of achieving this is to employ the Strategy pattern where a Context
class is closed for modifications (i.e. you can't change it's internal code at all) but is also open for extension through it's collaborating dependencies (i.e. the strategy classes).
In a more general sense any module is built to be extensible through it's extension points.
OCP is similar to DIP, right?
No, not really.
Although they both are discussing abstractions they're conceptually different. Both principles are looking at different contexts, OCP on one particular module and DIP on several modules. You can achieve both at the same time as with most Gang of Four design patterns, but you can still steer away from the path.
In the DIP example mentioned above, with the button and kitchen lamps, none of the kitchen lamps are extensible (nor currently has any requirement explaining they need to be). The design is breaking OCP but follows DIP.
A reverse (and a contrived) example would be a kitchen lamp be extensible (with the extension point being something like a LampShade
) but the button is still dependent on the lamps. It is breaking DIP but follows OCP.
Don't worry, it happens
This is actually something you'll see happen often in production code, that some part of it may break a principle. In larger software systems (i.e. anything larger than the examples above), you might break one principle but keeping the other usually because you need to keep the code simple. This is, in my mind, okay for small and self-contained modules, as they are in the context related to Single Responsibility Principle (SRP).
Once some module becomes complicated though you most likely need to look at it with all principles in mind and redesign or refactor it to some well-known pattern.