Dependency
Dependency is the loosest of the five relationships: no held reference, no field,
nothing that outlives a single method call. A ReportGenerator takes a
DateFormatter as a parameter, uses it, and forgets it existed the moment the method
returns. That's the whole relationship - and because it's so cheap, it's also the one
people most often either miss entirely or mistake for something heavier.
Definition: a borrow, not a hold
Two classes have a dependency when one of them uses the other without storing a
reference to it anywhere that survives the call: as a method parameter, a local
variable created and discarded inside a method, or a return type. The test that
actually matters: if DateFormatter's public interface changed, would
ReportGenerator need a code change to keep compiling? If yes, there's a dependency,
even though ReportGenerator never declares a DateFormatter field.
After buildHeader returns, ReportGenerator retains nothing about the DateFormatter
it was handed - the next call might receive an entirely different implementation, and
ReportGenerator neither notices nor cares.
UML notation
Dependency draws as a dashed line with an open arrowhead, pointing from the dependent class to the one it depends on. No diamond - dependency never implies ownership or a whole/part relationship, just a usage that shows up somewhere in the method signature or body.
Compare the dashes here to association's solid line: solid means "I keep a reference," dashed means "I glance at you and move on." Same open arrowhead shape, entirely different commitment.
Multiplicity - and why it usually doesn't apply
Association, aggregation, and composition all get multiplicity annotations because
they describe how many objects are connected at once - a standing structural fact
about the design. Dependency describes a single method call, not a standing
relationship, so "how many DateFormatters does a ReportGenerator depend on" isn't a
meaningful question the way "how many Orders does a Customer have" is. You'll rarely
see multiplicity written on a dependency arrow for exactly this reason - and if you find
yourself wanting to write one, that's often a sign the relationship is actually a held
reference (association) rather than a transient one.
Directionality
Dependency is essentially always unidirectional: ReportGenerator depends on
DateFormatter, and DateFormatter has no idea ReportGenerator exists, let alone
depends on it back. A "mutual dependency" - two classes each taking the other as a
parameter somewhere - isn't drawn with two arrowheads the way bidirectional association
is; it's usually just two separate, unrelated dependency arrows, and having a lot of
them between the same two classes is a sign worth pausing on (see common mistakes).
Worked example
Swap IsoDateFormatter for a UsDateFormatter at the call site and ReportGenerator
never notices - which is exactly the point of keeping the relationship this loose. No
field means no lifecycle to manage, no synchronization to worry about, and no reference
to leak.
Telling dependency apart from association
The test is entirely mechanical: is the reference held as a field?
- Held as a field, for the object's lifetime -> association.
- Received as a parameter, created as a local variable, or returned from a method, and never stored -> dependency.
A Driver with a private Car car; field has an association with Car. A
Driver.refuel(GasStation station) method that uses station only inside that one
method body has a dependency on GasStation - even though both relationships involve
one class "using" another, only one of them survives past the method call that
triggered it.
Common mistakes
- Drawing a dependency arrow for every single method call in a codebase. Taken
literally, almost every class depends on
String,List, and half the standard library - a diagram that includes all of that stops being readable. Reserve dependency arrows for relationships between the domain classes actually being modeled, not for incidental use of common utility types. - Mislabeling a held field as a dependency because "it's just used once." If a constructor stores the argument into a field, it's an association from that point on, regardless of how sparingly the field gets read afterward. Storage, not usage frequency, is the test.
- Treating dependency as if it were free of design cost. A dependency is loose,
not weightless - if
ReportGeneratorcalls five different methods acrossDateFormatter's interface, that's still a real coupling that breaks ifDateFormatterchanges its contract, even with no field involved.
Try it yourself: a PriceCalculator.applyDiscount(Coupon coupon, double price)
method takes a Coupon, reads its discount percentage, and returns an adjusted price -
PriceCalculator has no Coupon field anywhere. Is this a dependency or an
association? What single code change would flip it into the other one?
Check yourself
A ReportGenerator.buildHeader(DateFormatter formatter) method uses the parameter only inside that method body and stores nothing in a field. What relationship is this, and how is it drawn?