Safari Books Online is a digital library providing on-demand subscription access to thousands of learning resources.
An object depends on another object if, when one object changes, the other might be forced to change in turn.
Here’s a modified version of the Gear class, where Gear is initialized with four familiar arguments. The gear_inches method uses two of them, rim and tire, to create a new instance of Wheel. Wheel has not changed since you last you saw it in Chapter 2, Designing Classes with a Single Responsibility.
Code View:
Scroll
/
Show All 1 class Gear 2 attr_reader :chainring, :cog, :rim, :tire 3 def initialize(chainring, cog, rim, tire) 4 @chainring = chainring 5 @cog = cog 6 @rim = rim 7 @tire = tire 8 end 9 10 def gear_inches 11 ratio * Wheel.new(rim, tire).diameter 12 end 13 14 def ratio 15 chainring / cog.to_f 16 end 17 # ... 18 end 19 20 class Wheel 21 attr_reader :rim, :tire 22 def initialize(rim, tire) 23 @rim = rim 24 @tire = tire 25 end 26 27 def diameter 28 rim + (tire * 2) 29 end 30 # ... 31 end 32 33 Gear.new(52, 11, 26, 1.5).gear_inches |
Examine the code above and make a list of the situations in which Gear would be forced to change because of a change to Wheel. This code seems innocent but it’s sneakily complex. Gear has at least four dependencies on Wheel, enumerated as follows. Most of the dependencies are unnecessary; they are a side effect of the coding style. Gear does not need them to do its job. Their very existence weakens Gear and makes it harder to change.
An object has a dependency when it knows
The name of another class. Gear expects a class named Wheel to exist.
The name of a message that it intends to send to someone other than self. Gear expects a Wheel instance to respond to diameter.
The arguments that a message requires. Gear knows that Wheel.new requires a rim and a tire.
The order of those arguments. Gear knows the first argument to Wheel.new should be rim, the second, tire.
Each of these dependencies creates a chance that Gear will be forced to change because of a change to Wheel. Some degree of dependency between these two classes is inevitable, after all, they must collaborate, but most of the dependencies listed above are unnecessary. These unnecessary dependencies make the code less reasonable. Because they increase the chance that Gear will be forced to change, these dependencies turn minor code tweaks into major undertakings where small changes cascade through the application, forcing many changes.
Your design challenge is to manage dependencies so that each class has the fewest possible; a class should know just enough to do its job and not one thing more.
These dependencies couple Gear to Wheel. Alternatively, you could say that each coupling creates a dependency. The more Gear knows about Wheel, the more tightly coupled they are. The more tightly coupled two objects are, the more they behave like a single entity.
If you make a change to Wheel you may find it necessary to make a change to Gear. If you want to reuse Gear, Wheel comes along for the ride. When you test Gear, you’ll be testing Wheel too.
Figure 3.1 illustrates the problem. In this case, Gear depends on Wheel and four other objects, coupling Gear to five different things. When the underlying code was first written everything worked fine. The problem lies dormant until you attempt to use Gear in another context or to change one of the classes upon which Gear depends. When that day comes the cold hard truth is revealed; despite appearances, Gear is not an independent entity. Each of its dependencies is a place where another object is stuck to it. The dependencies cause these objects to act like a single thing. They move in lockstep; they change together.
When two (or three or more) objects are so tightly coupled that they behave as a unit, it’s impossible to reuse just one. Changes to one object force changes to all. Left unchecked, unmanaged dependencies cause an entire application to become an entangled mess. A day will come when it’s easier to rewrite everything than to change anything.
The remainder of this chapter examines the four kinds of dependencies listed above and suggests techniques for avoiding the problems they create. However, before going forward it’s worth mentioning a few other common dependency related issues that will be covered in other chapters.
One especially destructive kind of dependency occurs where an object knows another who knows another who knows something; that is, where many messages are chained together to reach behavior that lives in a distant object. This is the “knowing the name of a message you plan to send to someone other than self” dependency, only magnified. Message chaining creates a dependency between the original object and every object and message along the way to its ultimate target. These additional couplings greatly increase the chance that the first object will be forced to change because a change to any of the intermediate objects might affect it.
This case, a Law of Demeter violation, gets its own special treatment in Chapter 4, Creating Flexible Interfaces.
Another entire class of dependencies is that of tests on code. In the world outside of this book, tests come first. They drive design. However, they refer to code and thus depend on code. The natural tendency of “new-to-testing” programmers is to write tests that are too tightly coupled to code. This tight coupling leads to incredible frustration; the tests break every time the code is refactored, even when the fundamental behavior of the code does not change. Tests begin to seem costly relative to their value. Test-to-code over-coupling has the same consequence as code-to-code over-coupling. These couplings are dependencies that cause changes to the code to cascade into the tests, forcing them to change in turn.
The design of tests is examined in Chapter 9, Designing Cost-Effective Tests.
Despite these cautionary words, your application is not doomed to drown in unnecessary dependencies. As long as you recognize them, avoidance is quite simple. The first step to this brighter future is to understand dependencies in more detail; therefore, it’s time to look at some code.