Through the corroded dripping pipes of transitive dependencies, ripple effects gush. Programmers sometimes spend arduous days clambering down into the darkened building-tall labyrinths of plumbing for no other reason than to rip out some of those transitive dependencies so that predictability of cost of change might improve (amplification offering prime motivation). Those programmers that return often, after lengthy recuperation, babble of ancient principles glimpsed in new garb.
Consider the spoiklin diagram of figure 1, in which three methods -
c() - call a utility method,
d(), which in turn calls five others.
Figure 1: A simple system of nine methods.
How many transitive dependencies appear in figure 1? This essentially asks how many ways we can fall from the top to the bottom row. From
a() there is only one path, to
d(), but from
d() five possible choices exist. Method
a() can reach the bottom in five ways:
a() → d() → e()
a() → d() → f()
a() → d() → g()
a() → d() → h()
a() → d() → i()
Thus there are five transitive dependencies reaching from
a() and the same amounts from
c(), giving a total of 3 x 5 = 15. In general this configuration, where n elements depend ultimately on m elements, generates n x m transitive dependencies.
Now consider that the programmer wishes to further keep de-couple the three client methods from the implementation of the utility method,
d(). To do so, the programmer unsheathes the glittering dependency inversion principle and slashes a new interface with an abstract
d() method which a concrete class will then implement. Figure 2 shows the resulting system.
Figure 2: Dependency inversion.
In figure 2, the same three methods -
c() - call utility method
d() below but now via the new interface. The concrete class implementing the utility method also has a
d(), one on which no dependencies fall (clients invoke the interface method, not the concrete method) so that the concrete
d() appears on the top row, a dashed-line showing its inheritance dependency on its corresponding interface method below.
How many transitive dependencies does figure 2 contain? All four methods on top terminate on the abstract
d() below, yielding four transitive dependencies. Concrete method
d() above also has five dependencies on the original bottom-row methods, bringing the total number of transitive dependencies to nine.
Note, however, that a fundamental structural change has taken place. The number of transitive dependencies can be now expressed as a sum rather than a product. That is, the original system generated 3 x 5 transitive dependencies; the new system generates 3 + 6 transitive dependencies. In general we can say that where n original elements depend ultimately on m original elements, the above dependency inversion will reduce the number of transitive dependencies from n x m to n + m + 1. If n and m are large, this can be a significant saving.
Just as mathematical logarithms help ease calculation burden by reducing a product to an addition, so too can the dependency inversion principle help reduce the number of transitive dependencies from a product to an addition. Furthermore, this application of the dependency inversion principle has also reduced the system's depth, "depth" being the average length of the system's transitive dependencies. In figure 1, all the transitive dependencies are of length three; in figure 2, they are all two elements long. Given that ripple effects cause most destruction when wormed deep in a vast profusions of transitive dependencies, the dependency inversion principle would seem a double benefactor.
Old crotchety principles sometimes surprise. The dependency inversion principle has long earned respect from programmers for its prowess at smashing the rigidity and fragility of otherwise un-lubricated systems. Yet this fails to map the full extent of its powers. Seen from a purely structural perspective, it can be used to reduce both the number and length of transitive dependencies squirming through a program.