What he's saying here is that you should avoid a scenario where a base class takes a dependency to meet the need of a descendant. Let's look at the case of a Switch and its descendant the TimedSwitch. The TimedSwitch is a Switch that resets itself after a certain amount of time. We have the TimedObject class already so it would make sense for TimedSwitch to derive from it...however TimedSwitch already derives from Switch. So in order to meet the needs of the TimedSwitch, Switch will inherit from TimedObject even though it doesn't need that functionality itself.
The Switch (abstraction) is dependent on the details of TimedSwitch. To fix this violation of the DIP, we have TimedSwitch implement ITimedObject and delegate to a contained TimedObject class (prefer composition over inheritance).
Another more generic example is the layered approach. The higher level layers are abstractions and in most cases they depend on the details. DIP says that instead, the lower layers should conform to an interface which they both take dependency on. Thus the dependency is inverted from Higher Layer depending on lower layer to both layers depending on an interface.
Dependency Inversion in OOP means that you code against an interface which is then provided by an implementation in an object.
Languages that support higher language functions can often solve simple dependency inversion problems by passing behaviour as a function instead of an object which implements an interface in the OO-sense.
In such languages, the function's signature can become the interface and a function is passed in instead of a traditional object to provide the desired behaviour. The hole in the middle pattern is a good example for this.
It let's you achieve the same result with less code and more expressiveness, as you don't need to implement a whole class that conforms to an (OOP) interface to provide the desired behaviour for the caller. Instead, you can just pass a simple function definition. In short: Code is often easier to maintain, more expressive and more flexible when one uses higher order functions.
An example in C#
Traditional approach:
public IEnumerable<Customer> FilterCustomers(IFilter<Customer> filter, IEnumerable<Customers> customers)
{
foreach(var customer in customers)
{
if(filter.Matches(customer))
{
yield return customer;
}
}
}
//now you've got to implement all these filters
class CustomerNameFilter : IFilter<Customer> /*...*/
class CustomerBirthdayFilter : IFilter<Customer> /*...*/
//the invocation looks like this
var filteredDataByName = FilterCustomers(new CustomerNameFilter("SomeName"), customers);
var filteredDataBybirthDay = FilterCustomers(new CustomerBirthdayFilter(SomeDate), customers);
With higher order functions:
public IEnumerable<Customer> FilterCustomers(Func<Customer, bool> filter, IEnumerable<Customers> customers)
{
foreach(var customer in customers)
{
if(filter(customer))
{
yield return customer;
}
}
}
Now the implementation and invocation become less cumbersome. We don't need to supply an IFilter implementation anymore. We don't need to implement classes for the filters anymore.
var filteredDataByName = FilterCustomers(x => x.Name.Equals("CustomerName"), customers);
var filteredDataByBirthday = FilterCustomers(x => x.Birthday == SomeDateTime, customers);
Of course, this can already be done by LinQ in C#. I just used this example to illustrate that it's easier and more flexible to use higher order functions instead of objects which implement an interface.
Best Answer
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.