I am trying to explain the dependency inversion principle to my (mostly junior) colleagues. How can we define which one is the "high-level policy" and which one is the "low-level detail" in a software? For example, if our software automates workflow of several business applications, why do we say that the workflow automation is the high-level policy and the business applications are the details?
The classic approach in software reuse is to build components which depend on nothing else (that is what makes them low-level), and then build higher-level components which depend on lower-level components. "high level" and "low level" are specifially determined by the direction of the dependency, which is not inherent to the function of the component, but often just an architectural decision.
So, when single business applications are build with no dependency to workflow automation, but your work-flow controller has direct dependencies to the business application, then it should be clear that workflow automation is a "high level policy" and the business application is a "low level" component. Note that this structure is not obligatory - if your workflow automation component is a general framework, which is not coupled to your specfic business applications either, but can be configured to serve different applications, then you have already started to apply the DIP. In this situation, the "high level"/"low level" separation may not make sense between those two things any more.
So, the name "dependency inversion" is somewhat misleading - since dependencies are not "inversed", but completely removed (or to be more precise: changed from beeing compile time dependencies into run time dependencies).
Note: this has been completely rewritten from my earlier example
Think about power sockets. In any given nation, the high-level policy is that power sockets are always the same. It doesn't matter where you get you electricity from (coal, gas, nuclear), the sockets on the wall should always put out the same amount of power, through the same set of connectors.
Now you can plug any device into that socket, because they all have a common interface, the "plug". The high-level policy never has to dictate any part of that implementation detail. Just plug something in and it goes.
Now if you have a device that doesn't want AC power -- maybe it runs on a 7V DC circuit -- you can still use that high-level policy, you just need some kind of adapter between the power supply and the device. And, because everyone has the same high-level policy, the manufacturer can build that into the implementation, without any change to the high-level policy. The person connecting the implementation to the policy (you, plugging your laptop in) doesn't really need to understand either.
Further, if the manufacturer wants to sell the same device in another country, all they have to do is develop a different adapter. So the same implementation can work with multiple policies while the same policy can run multiple implementations.
This is a perfect example of dependency inversion.
But here's the interesting bit: Go back to what I first said. "It doesn't matter where you get you electricity from." This is also an implementation detail. The high-level policy is still that all power sockets are the same shape and emit the same type of power. The low-level implementation details are both where the electricity comes from and what it runs.
In programming terms, that means the high-level policy is the interface (Where a language supports it. Another form of DI is duck-typing.) that an API provides and the application consumes, and the low-level implementation details are both the application consuming it and the API itself, neither of which need to understand each other.
Adapters may be used to fit the same implementation into different policies.
I use a simple image to explain DIP. The classic view of software development is as a construction process with each layer sitting on top of the lower layers that support it. Using the Dependency inversion Principle is more like building a mobile.
Rather than the higher layers sitting on the lower layers, the higher layers in the mobile interface with the lower layers via the string that connects them. In a way, the lower layers depend on that interface for support (without the string they'd fall). That's DIP in a nutshell.