Design Patterns · Foundations
Relationships Between Objects
Read the six links in every class diagram: dependency, association, aggregation, composition, implementation, and inheritance.
This lesson builds on Objects, Classes, and the Four Pillars. Pattern diagrams are mostly arrows. Each arrow style names one kind of relationship, and confusing two of them hides the design.
Besides inheritance and implementation, objects relate in four more ways. They range from a weak uses link to a strong owns and controls link.
Learn the six relationships one at a time
Try it
Pick a relationship to see its notation and its code
A change to Order might force a change to ReceiptPrinter. The printer only touches an order while print runs. It keeps no reference.
class ReceiptPrinter {
print(order: Order) { // Order appears only as a parameter
return order.lines().map(formatLine).join("\n");
}
}A courier always has access to its vehicle through a field, so the link lasts beyond one method call. Association is a dependency that is always reachable.
class Courier {
constructor(private vehicle: Vehicle) {} // a lasting link
deliver(parcel: Parcel) {
this.vehicle.driveTo(parcel.address);
}
}A playlist holds many tracks, but a track exists on its own and can sit in many playlists. Deleting the playlist does not delete the tracks.
class Playlist {
private tracks: Track[] = [];
add(track: Track) { // the track was created elsewhere
this.tracks.push(track);
}
}An order is made of line items, and a line item has no meaning outside its order. The order creates its items and they disappear with it.
class Order {
private items: LineItem[] = [];
addProduct(product: Product, qty: number) {
this.items.push(new LineItem(product, qty)); // the order owns the item's life
}
}Song provides the methods that Playable declares. Any code that expects a Playable can receive a Song.
interface Playable {
play(): void;
}
class Song implements Playable {
play() { /* stream the audio */ }
}Podcast inherits both the interface and the implementation of MediaItem, and can extend them. A podcast can be used anywhere a media item is expected.
abstract class MediaItem {
constructor(protected title: string) {}
describe() { return this.title; }
}
class Podcast extends MediaItem {
constructor(title: string, private episode: number) { super(title); }
describe() { return super.describe() + " #" + this.episode; }
}UML shows classes, but the relationships describe objects
A university object may contain many department objects, even though the diagram draws one box for each class. Add counts such as 1..* at the ends of a line only when the context does not make them obvious.
Tell dependency and association apart
Dependency is the weakest link. Class A depends on class B if changing B might force a change in A. That happens whenever A names B: as a parameter type, a return type, or a constructor call. You weaken a dependency by naming an interface instead of a concrete class.
Association is a dependency that lasts. Object A always has access to object B, usually through a field. One example shows both:
class Coach {
constructor(private team: Team) {}
run(drill: Drill) {
this.team.practice(drill.steps());
}
}
If someone renames Drill.steps(), Coach breaks. That is a dependency. If someone renames Team.practice(), Coach also breaks, but Coach holds the team in a field and can use it from any method. That makes Team both a dependency and an association.
Diagrams usually do not draw every dependency. Real code has too many. Draw the ones that explain your idea.
Tell aggregation and composition apart
Both describe a whole that contains parts. The question is who controls the parts' lifetime.
| Aggregation | Composition | |
|---|---|---|
| Symbol | Hollow diamond at the container | Filled diamond at the owner |
| Can the part exist alone? | Yes. A track exists without any playlist. | No. A line item has no meaning outside its order. |
| Can the part belong to several wholes? | Yes | No |
| Who creates and destroys the part? | Someone else | The whole |
“Composition” in a principle usually means both
In “favor composition over inheritance,” composition means any object that holds and delegates to another object. It covers aggregation too. People say composition because it sounds better, not because they forgot the difference.
Check yourself
A track remains available after its playlist is deleted. Which ownership model fits?
Correct. The playlist references tracks whose lifetime is independent.
Not quite. That would make the parts depend on the owner's lifetime.
Rank the links from weak to strong
| Relationship | What it tells you about A and B |
|---|---|
| Dependency | Changing class B may affect class A |
| Association | Object A knows object B. Class A depends on class B. |
| Aggregation | Object A knows object B and is made up of B objects. Class A depends on class B. |
| Composition | Object A knows B, is made up of B, and manages B's lifetime. Class A depends on class B. |
| Implementation | Class A defines the methods that interface B declares. A can be treated as B. |
| Inheritance | Class A inherits B's interface and implementation and can extend them. A can be treated as B. |
Notice that every row includes a dependency. Inheritance is the strongest dependency of all: a change to the parent can break every child.
Check yourself
A method uses a parameter briefly, while another class keeps the object in a field. Which connection normally lasts longer?
Correct. The reference remains reachable after a single method returns.
Not quite. A parameter-only use does not itself establish a lasting stored connection.
Retrieve and apply
Check yourself
A Building creates its Floor objects in its constructor. Floors are never shared and are destroyed with the building. Which relationship is it?
Not quite. Aggregation allows the part to live on its own or in several wholes. These floors cannot.
Not quite. Association says Building knows Floor. That is true but too weak. The building also owns the floors' lifetime.
Correct. The whole creates the parts, the parts are not shared, and they die with the whole. That is composition, drawn with a filled diamond.
Check yourself
InvoiceMailer.send(invoice: Invoice) reads the invoice once and keeps no reference. How should the diagram show Invoice?
Correct. Invoice appears only as a parameter. A change to it can break the mailer, but there is no lasting link.
Not quite. Association needs a lasting link, such as a field. A parameter is not one.
Not quite. Nothing extends anything here.
Continue to Design Principles Behind the Patterns to see which goals these relationships serve.
Source: Alexander Shvets, Dive Into Design Patterns (深入设计模式), Chinese edition v2021-1.25. Relationships between objects, printed pages 20–25. Explanations, examples, and exercises are adapted for this course.