Dependency Inversion Principle

DIP:
A. High level modules should not depend upon low level modules. Both should depend upon abstractions.
B. Abstractions should not depend upon details. Details should depend upon abstractions. (http://www.objectmentor.com/resources/articles/dip.pdf)

Forces

We wish to avoid designs which are: Inversion

"Structured" methods of the 1970's tended towards a "top-down decomposition", which encouraged high-level modules to depend on modules written at a lower level of abstraction. (More modern procedural approaches depend more on databases and less on functional decomposition at the large-scale level. Thus, they are often "flatter" now.) This principle stems from the realization that we must reverse the direction of these dependencies to avoid rigidity, fragility and immobility. [edited from Robert's original to reflect modern procedural philosophy]

Examples

In RobertMartins article, he uses a simple copying program as an example. The program copies characters from the keyboard to a printer. Modifying the program to support arbitrary output devices is inelegant since the copy "routine" implicitly depends on the keyboard reading code. A better architecture would define interfaces for readers and writers, thus the program can be easily adapted to support reading and writing from additional code for new devices. (This example seems to explain some of the idioms present in the java.io package.)

The ObserverPattern can be considered a two-layered architecture, where the model is the lower-level module and the higher level module is the observer.

See also:
One of the PrinciplesOfObjectOrientedDesign.


Controversies

Some of this sounds like typical pro-OO procedural-bashing. The "device driver" examples may not extrapolate to all situations and assume that adding new devices is more common than adding new operations to existing devices (ExtrapolatingDeviceDrivers). What is "rigid" often depends on assumed ChangePatterns. There is no free lunch in such cases, only estimations of paths of least costs. GoldPlating for "reuse" that is unlikely to happen in practice can often lead to unnecessarily complicated code (such as excess redirection). An economcially-sound design needs to factor in probabilities, which the article either ignored or assumed without stating. And "high-level" and "low-level" and "detail" versus "abstraction" are not well defined. Abstractions can involve a lot of nitty details also.

And, too many layers of indirection is sometimes anti-YagNi.

What a load of crap. This section should be deleted.

On what grounds, Buster! The original article is not scientific; there's the REAL problem. It does not scientifically measure "rigid" or "fragile" or "immobile".

It doesn't need to. The DependencyInversionPrinciple, and the SOLID principles in general, have been proven in practice over and over and over again. They are as fundamental to OO programming as avoiding global variables or GOTOs in procedural programming.

Plus it's logical that if you add extra code to cater to "reuse" of something, and reuse never happens (or happens differently than anticipated), you've wasted code maintainer eyeball real-estate and code volume on nothing. Thus the probability and "shape" of reuse does matter. Any Vulcan would realize that. Screw you Ferengi Fad Suckers!

It isn't "extra code". It's a fundamental approach to OO programming.

Well, OO'ers are often wrong, driven by a fake idealism about how the real world changes over time.

Really?

Yes! ArgumentFromAuthority is a bad habit. Pick among multiple designs for an articulatable reason(s), not because because "X says design Y is always best practice".

I doubt any OO programmer applies the DependencyInversionPrinciple just because Robert Martin says so. OO programmers apply the DependencyInversionPrinciple because it works. Robert Martin didn't invent the DependencyInversionPrinciple; it was de facto good practice long before he documented it.

It "works" in some cases, but not others. A claim of being universal requires evidence. The reader should not be expected to take your word for it. Perhaps it can be considered a "design to consider", but YagNi is also something that should be considered. You cannot dictate that DIP always trumps YagNi without evidence. Predicting future growth patterns is not an easy job; and often requires domain experience. It's probably possible to take any bit of code and bloat it up say 20x the YagNi size by GoldPlating for every conceivable potential future what-if change scenario.

Really, when doesn't it "work"? If we're talking about evidence, can you provide evidence that given modern IDEs -- which permit automatically extracting interfaces and the like -- that it genuinely represents sufficiently extra work to be considered YagNi? Or, alternatively, would it be considered inherently good practice because it reduces coupling, which improves testability, maintainability, and replace-ability?

Lovely, a Bloat-A-Matic. I'm going to send in the YagNi/agile Mafia to beat up the OOP Mafia. Indirection can potentially improve a lot of things, but also can make for spaghetti code. I'm not saying it outright doesn't run, but rather can make for code that's hard to read because references reference references to other references. One needs to weigh the trade-offs on a case-by-case basis, not create indirection by default.

On what basis have you determined that applying the DependencyInversionPrinciple is "bloat"? Can you show how it represents "indirection" that is "hard to read"?

The original code in the example was easier to read. Now in the example by itself the added indirection is not a readability problem, but if packed into production code with all the other details and all the other indirection going on, yes, it can become a readability problem. Thus, if it's unlikely that we are to add many new "devices" (or whatever noun the pattern is being applied to), then the extra code is not worth it. I recommend writers do a probability estimate on the kinds of future changes and not add indirection and extra code as a standard practice by itself. I don't always agree with YagNi, I would note, because probability estimates sometimes do identify high-change areas. However, they are often not the "sub-type" pattern that OO books tend to assume, per below.

But DependencyInversion isn't indirection. In modern OO languages, it means coding to interfaces rather than to concrete implementation classes, but that doesn't represent indirection per se. A call to myInstance.myFoo() is syntactically the same whether instance is an interface or a concrete implementation class. The only difference is in the type declaration of myInstance, which will be an interface (or abstract base class) -- say, MyInterface -- rather than the concrete implementation class. Of course, the concrete implementation class will implement or extend MyInterface.

I would note that variation I find in the wild is often smaller than that of a "device", per original example and the variation activation is controlled from settings stored in a database, not code instances, as described near the bottom of HofPattern.

Do you believe that the "variation [you] find in the wild" is representative of object-oriented programming in general?

I meant variation on a domain theme in code, not coder style variation. Sorry for the confusion. As far as what kinds changes one encounters, it indeed may vary per domain/industry. But this is my original point: "Always prepare for change pattern X" is wrong as a general suggestion. It may be true of a given domain, but I already agreed ItDepends. DecisionMathAndYagni shows the smart way to make such decisions. And it documents the reasons for decisions rather than just "X said so".

Do you have evidence that change patterns in OO code vary from domain to domain? Can you give examples?

Why are we only talking about OO code?

The DependencyInversionPrinciple applies to OO code. It's the 'D' in SOLID, an acronym for the PrinciplesOfObjectOrientedDesign.

There seems to be some confusion here. The bottom line is deciding the best way to code and manage "variations on a theme" (VOAT). We have domain objects/process "instances" (in a loose sense) where there are commonalities and differences. The open question is whether to ALWAYS do them up the way Martin's example says so, or whether ItDepends based on ChangePattern analysis (which should involve domain experience). There are MANY strategies for dealing with VOAT, each with a different trade-off profile.

What are all the strategies for dealing with VOAT? Can you show examples of varying trade-off profiles?

I don't currently have a catalog.

Then can you give some examples?

FeatureBuffetModel is one strategy. The implementation tends to resemble this for a given process of a given "object" category:

   func processSomething() 
      if feature_A then
        if feature_R then
          ...
        end if
        ...
      else
        ...
        if feature_C and (feature_Q or feature_J)
          ...
        end if
      end if
      if Not feature_M
        ...
      end if
   end func

That seems to be the opposite of DependencyInversionPrinciple. Instead of coding to a high-level abstraction, you're coding to every low-level implementation. Isn't that precisely what DependencyInversionPrinciple tries to avoid?

Sub-classing to get the same thing would create a combinatorial explosion. A hierarchy is an inadequate structure for managing such.

If a combinatorial explosion is risked, then what you're modelling probably isn't hierarchical. I agree. I've seen such things when modelling business rules, which tend not to have any abstract basis. Given a set of rules 'n', there is no meaningful abstraction of 'n'. Objects certainly help when building a generic rule processor, but aren't much help when it comes time to use it your rule processor to implement the rules that model a business scenario.

Others have agreed that OO seems to work better at building "computing space" tools/abstractions and not so well at business domain modeling.

One of those "others" was me.

But you were wearing a different suit.

It was the same suit in different light.

In practice, what you mean by "dependency" will depend on the language. In Smalltalk, whatever handful of messages the client happens to send to a server is a protocol implicitly. It doesn't have to be named to satisfy the type checker. The client has no dependency on the server beyond them. So inserting the abstract base class buys you very little. You might as well just group the messages together in the IDE and label them there without adding the new class.

In CeePlusPlus, the traditional C++ and link formats mean that client code can depend on the size of the server object, the layout of its vtbl, and many other incidental details. The abstract base class is a way of making these details more stable.

It seems to me that the dependencies the inversion principle are trying to avoid are just artifacts of premature optimisation in the language implementation. -- DaveHarris


Circular dependencies between VisualBasic projects are worse than a nuisance -- they prevent you from being able to build executables from sources.

Specific examples?

Proper application of the DependencyInversionPrinciple -- via implementation of VB/COM interfaces -- resolves the problem.

Alternative for VisualBasic: Use "As Object" (IDispatch interface). Doing so will remove compile-time circular dependencies, at the expense of type safety and compile time type checking. (The projects will depend on IDispatch, rather than referencing each other.)


See also DependencyInversionAndXp and ContextIndependence
CategoryModelingLawsAndPrinciples

EditText of this page (last edited March 21, 2013) or FindPage with title or text search