Object relationships: links, sharing and ownership

Choose association, shared aggregation or composition from a stated object-lifetime contract. Relationships and copying · Object-oriented programming.

You remove a song from one playlist and someone warns that every other playlist will lose it too. Another diagram calls a private field “composition” without saying who owns the object. Before choosing an arrow, ask what the application promises about identity, sharing and lifetime.

An object is one instance with its own identity. A reference lets another object find it. Two references can identify the same object without copying it. In the scene, each playlist contains a reference to one catalog Song. Follow the links before reading the diamonds.

Two playlists, one Song

Read the steps as text

  1. Song S1 is named Lark, and only the variable catalog refers to it. Playlists A and B have no song linked yet.
  2. Playlist A's song link now points at Song S1. A reference reuses the Song that exists, where a copy would have made a second one.
  3. Playlist B's song link now points at Song S1. No second Song was built, so both playlists reach the same object.
  4. The program clears Playlist A's song link, which pointed at Song S1. The Song is still there, and Playlist B still reaches it.
  5. Removing one membership deleted nothing else, so the Song, Playlist B's link and catalog all remain.

An association records a relationship between objects: a playlist knows a song, or a reviewer knows an episode. It does not, by itself, promise exclusive ownership, copying, or deletion. An arrowhead can show which side holds a reference to the other. A line without arrowheads need not claim that either side holds one.

Shared aggregation adds a whole/part interpretation, conventionally a hollow diamond at the whole. Its precise meaning depends on the model. Here we explicitly choose independently cataloged Songs that may appear in several Playlists. Removing one membership leaves the Song available elsewhere. We could draw plain associations and state the same sharing contract in words.

Do not turn “has a field” into a complete ownership proof. A field can reference a shared catalog item, a uniquely owned part, or a temporary collaborator. The declarations may look similar while the requirements differ. Decide what the link means before selecting a UML symbol.

One whole, a different promise

Now model an Episode and its opening Cue. Our requirement says a Cue is an exclusive logical part of one Episode, cannot simultaneously belong to another Episode, and is treated as deleted by the application (no longer listed or usable) when its owning Episode is deleted. We call that logical deletion: the application stops using the object, whether or not memory is reclaimed. Those requirements justify composition, shown with a filled diamond at the whole.

One Episode owns its Cue

Read the steps as text

  1. The variable shows refers to Catalog main, which lists Episode E1. Under our rule E1 alone owns Cue C1, and the filled diamond marks that exclusive part.
  2. Only Episode E1's cue link reaches Cue C1, which is what exclusive ownership looks like.
  3. The program clears Episode E1's cue link, which pointed at Cue C1. The delete routine lets go of the part the Episode owns.
  4. The program clears Catalog main's episode link, which pointed at Episode E1. The application now treats Episode E1 and Cue C1 as deleted.
  5. Our rule deletes the part with its whole, and this view cannot show memory being reclaimed.

The diamond describes the domain, not a garbage-collector instruction. Suppose a reviewer saved a reference to the Cue (an alias) before the Episode was deleted. The catalog no longer lists the Episode, so the domain says the Cue is gone, yet that saved reference still reaches the same object. Deleting from the domain and freeing memory are separate events, and the program must enforce its own editing rules. The diagram does not automatically prevent an accidental alias, revoke an exposed reference, or choose a collection time.

A saved reference outlives the Episode

Read the steps as text

  1. The variable shows refers to Catalog main, which lists Episode E2, and E2 owns Cue C2. A reviewer's variable named saved refers to nothing yet.
  2. Variable saved now refers to Cue C2. That makes a second reference to the Cue, beside Episode E2's cue link.
  3. The program clears Episode E2's cue link, which pointed at Cue C2. The delete routine lets go of the part, but saved still holds it.
  4. The program clears Catalog main's episode link, which pointed at Episode E2. The application now treats Episode E2 and Cue C2 as deleted.
  5. Logically deleted, yet saved still reaches Cue C2, so only the program's rules can prevent its use.

Composition also does not mean a part can never be moved. A model may permit detaching a part before deleting its old whole and assigning it to a new whole. The important restriction is the ownership rule at a given time. Our tiny Episode example does not expose a transfer operation.

Read the code and the contract together

These complete examples demonstrate sharing. a and b hold the same Song reference; neither Playlist constructor copies the Song. Java == and Python is test identity here. The output is true in Java and True in Python. This says nothing about exclusive ownership because our stated catalog policy deliberately permits sharing.

class Main {
    static final class Song {
        String name;
        Song(String name) { this.name = name; }
    }
    static final class Playlist {
        Song song;
        Playlist(Song song) { this.song = song; }
    }
    public static void main(String[] args) {
        Song catalog = new Song("Lark");
        Playlist a = new Playlist(catalog);
        Playlist b = new Playlist(catalog);
        System.out.println(a.song == b.song);
    }
}
class Song:
    def __init__(self, name):
        self.name = name

class Playlist:
    def __init__(self, song):
        self.song = song

catalog = Song("Lark")
a = Playlist(catalog)
b = Playlist(catalog)
print(a.song is b.song)

Try two edits. Setting catalog.name = "Wren" changes the one shared Song, so a.song.name and b.song.name both read Wren. Setting catalog = new Song("Kite") in Java, or catalog = Song("Kite") in Python, only moves the local variable; a.song and b.song still hold the Wren Song, and a.song is b.song stays true (Java == true). Mutating a target and rebinding a reference are different operations. The scenes attach and detach by assigning song, so the examples above leave that field non-final.

Edit the Song or move a variable

Read the steps as text

  1. Playlists A and B and the variable catalog all refer to Song S1, which is named Lark. That makes three references to one object.
  2. The program changes the name of Song S1 from "Lark" to "Wren". Playlists A and B and catalog all reach that same Song, so each now sees Wren.
  3. The program builds a new Song S2. Variable catalog now refers to Song S2 instead of Song S1. Only the variable moved, so the playlists still hold Song S1.
  4. Both playlists still reach Song S1, which still shows the name Wren.
  5. Editing the one Song shows through every holder, and moving a variable changed no stored link.

To keep a Cue exclusive, keep it private and never hand it out. In Java, a private Cue field plus no method that returns it makes the compiler reject access from any other top-level class (classes nested in the same file can still see private members). The scene's shows.episode.cue = null; stands for the Episode's own delete routine, which would be a method inside Episode. In Python, a leading underscore (self._cue) only signals the intent; nothing stops outside code from reading it, so the rule must be kept by the team. If Episode must show cue information, return a small value such as the cue text, not the Cue object. Also write down whether sharing, transfer and deletion are allowed.

Recap

  • A stored reference can point to an existing object without creating a copy.
  • Shared aggregation needs a stated convention; composition adds exclusive whole/part lifecycle responsibility.
  • Logical deletion and runtime memory collection are separate concerns.
  • Removing a song from one playlist removes that playlist’s link only; the Song and every other playlist’s link stay.
  • Editing the shared Song shows through every holder, but reassigning one variable moves only that variable.
  • A saved reference keeps a logically deleted part reachable.
  • Keep a part exclusive by never handing it out: Java checks this, Python leaves it to the team.
Go deeper

“Prefer composition over inheritance” often means assembling behavior through collaborators rather than inheriting implementation. That broad design advice does not require a filled UML diamond for every collaborator. Keep behavioral delegation and strict composite ownership distinct.

UML 2.5.1 defines the composite end as the whole and constrains a part’s composite ownership. Its shared-aggregation semantics are deliberately dependent on the application area and modeler. Explain the actual contract rather than relying on a diamond to carry unstated rules.

Check your understanding, then continue to shallow and deep copy to choose which links an edit must isolate. Practise reference rewiring in Linked List Fundamentals, where changing a link is different from mutating the node it reaches.