- 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)
We wish to avoid designs which are:
- Rigid (Hard to change due to dependencies. Especially since dependencies are transitive.)
- Fragile (Changes cause unexpected bugs.)
- Immobile (Difficult to reuse due to implicit dependence on current application code.)
"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
. [edited from Robert's original to reflect modern procedural philosophy]
s 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.)
can be considered a two-layered architecture,
where the model is the lower-level module and the higher level module is the observer.
- [Martin95] Robert C. Martin, Object Oriented Design Quality Metrics: An Analysis of dependencies, ROAD, Vol. 2, No. 3, Sep-Oct, 1995.
- This principle occurs as a result of the OpenClosedPrinciple and the LiskovSubstitutionPrinciple.
- [Martin2002] Robert C. Martin, Agile Software Development: Principles, Patterns, and Practices, p.127-135
One of the PrinciplesOfObjectOrientedDesign
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 ChangePattern
s. 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" or "high" versus "low" abstraction.
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. [Removed insult mentioning Ferengi's.]
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.
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
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? And the default "truth value" is NOT that they don't change, but rather "unknown". I suggest one know the domain to make an estimate about likely future change patterns when two or more possible change "paths" are found.
The DependencyInversionPrinciple applies to OO code. It's the 'D' in SOLID, an acronym for the PrinciplesOfObjectOrientedDesign.
- It applies ONLY to OOP code? So if the same app is coded in FP or procedural, it doesn't apply? What exactly is Martin suggesting then?
- Martin wrote specifically about OO code. In FP and procedural code, the same underlying principle -- coding to high-level abstractions rather than low-level implementations -- applies, but the mechanisms are different.
- And whether to wrap something in a generic (intended) interface is also subject to frequency analysis.
- How do you apply "frequency analysis", i.e., what numerics or key indicators lead to what decisions? How do you decide when doing "frequency analysis" is taking more time and effort than applying DependencyInversionPrinciple in the first place?
- I don't have a precise algorithm, but rather apply experience. Blunt rules are not the smart way to design. SoftwareEngineeringIsArtOfCompromise. (See also SimulationOfTheFuture.)
- That sounds rather ad hoc and subjective. Wouldn't it be safer to apply DependencyInversionPrinciple wherever you might have applied "frequency analysis"?
- You tell me, AND give a clear and full reason for it. Yes it is rather ad-hoc. Otherwise, robots would write all our code. Software engineering is largely an art.
- The clear and full reason is that it reduces coupling, which is always good.
- Even at the expense of more code? Are you entirely refuting the principle of YagNi? Plus, "coupling" is poorly defined. See CouplingAndCohesion.
- Given a choice between eliminating the minimal "more code" that DependencyInversion represents, YagNi, and improved coupling, I'll always choose improved coupling. Less code is not necessarily better code! Coupling may be poorly defined, but it's well understood. YagNi in general is an excellent principle, but I don't consider development that follows the DependencyInversionPrinciple to be YagNi. Following the SOLID principles is simply good OO coding practice.
- I have to disagree. You don't wrap things in extra interfaces or add layers of indirection unless future changes are fairly likely to come along that can take advantage of them. If a simple IF statement does the job and will likely be just fine in the future, then use it. K.I.S.S. Class clutter slows down reading, at least for those of us who don't have FastEyes.
- Obviously, the choice isn't between a simple IF statement vs coding to an interface. DependencyInversion presumes the existence of classes, for which you could have coded to a concrete implementation class but (following the DependencyInversionPrinciple) you extract its interface and code to that instead. It's only one interface per hierarchy of concrete implementation classes, and as noted above, it isn't indirection per se. Coding to an interface is identical to coding to a concrete implementation class. As for FastEyes, if you can't stand the heat... :-)
- Is it extra code, yes or no? As far as "standing the heat", perhaps GoldPlating to keep away SlowEyes? is a form of manipulative job security. Other developers have complimented my "sleak & slim" code when done well, and complained about over-engineered experiments that didn't pay off. Those other developers (including future maintainers) affect one's standing in the industry. We need to code for average eyes or feel the wrath. I do care what other developers think of my code and realize that I cannot pick and choose who will have to maintain my code when I'm not around. "Only Mozart can touch my precious code!" won't cut it. (Listing 2 does have an IF statement for each "device".)
- There is extra code -- the interface definition, plus the concrete implementation classes' identification of implementing the interface. Otherwise, it is the same. As for "GoldPlating to keep away SlowEyes? [as] a form of manipulative job security", that's just paranoid.
- Can never rule out subconscious motivation. I'm sure I've done sub-optimal stuff for subconscious reasons I'm not aware of. Human nature. Anyhow, my assessment is that one should not add extra classes unless there's likely a future need for it. If your opinion differs, so be it. There's already plenty of topics debating the shades of YagNi.
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?
is one strategy. The implementation tends to resemble this for a given process of a given "object" category:
if feature_A then
if feature_R then
if feature_C and (feature_Q or feature_J)
if Not feature_M
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.
, 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.
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