M6 — Composition and Structural Change

SENG 365 — Software Engineering

Neil Ernst

University of Victoria

2026-10-21

Module Goals

Design Judgment

By the end of this module, you should be able to:

  • recognize when objects and groups need uniform treatment;
  • compare composition with conditionals and subclass proliferation;
  • sketch Composite, Decorator, and Command from their design forces;
  • use a sequence diagram to expose run-time coupling; and
  • decide whether a message chain violates the Law of Demeter.

Composition and Composite

Preparation

Before class: Read Chapter 6, Sections 6.1–6.3.

Entry Decision

A theatre program can contain a movie, a double bill, or a festival of programs.

  • Should every client branch on the kind of show, or should individual and grouped shows share an operation?
  • Name the operation.

Movie-Theatre Artifact

int duration(Object show) {
    if (show instanceof Movie movie) return movie.duration();
    if (show instanceof DoubleBill bill)
        return duration(bill.first()) + duration(bill.second());
    if (show instanceof Festival festival)
        return festival.shows().stream()
            .mapToInt(this::duration).sum();
    throw new IllegalArgumentException();
}

What new structure causes another branch?

Composite Studio

Design a component type and object graph (see 6.2) for:

  • Movie as a leaf;
  • Program as a group;
  • nesting programs inside programs; and
  • computing duration uniformly.

Produce a class diagram and one example object graph. State what client code becomes simpler.

Put the Graph Under Pressure

Can your design represent:

  • an empty program;
  • a program containing itself;
  • the same movie twice;
  • a cycle of programs; or
  • a program exceeding one day?

Uniform treatment simplifies clients but can make invalid object graphs easier to construct. Who enforces the invariant?

Null Object as a Choice

The Null Object example is useful when an absent child must safely receive the same operation:

final class NoShow implements Show {
    public int duration() { return 0; }
}

Compare it with an empty collection, Optional<Show>, and null. Use it only when “do nothing” has valid domain meaning.

Synthesis

  • Composition builds run-time structures from objects.
  • Composite lets clients treat leaves and groups uniformly.
  • The component interface must be meaningful for both.
  • Patterns do not enforce valid configurations.
  • A factory, constructor, or client may still own graph invariants.

Exit Note

Composite simplified __________, but __________ must still enforce __________.

Decorator and Command

Preparation

Before class: Read Chapter 6, Sections 6.4–6.8.

Entry Decision

A coffee shop offers milk, syrup, extra shots, and whipped cream in any combination.

Compare one subclass per combination with one class containing option flags. Which change hurts each design?

Coffee-Shop Decorator

interface Beverage {
    Money cost();
    String description();
}

A decorator implements Beverage, wraps another Beverage, delegates, and adds one responsibility.

Decorator Studio

Sketch:

  • one basic beverage;
  • two decorators that can be chained;
  • the object graph for a customized order; and
  • the calls made by cost().

Then compare the design with a simple list of additions. What justifies the pattern’s extra indirection?

Real-World Recognition

InputStream input =
    new BufferedInputStream(
        new FileInputStream("orders.json"));

The wrappers retain the component type while adding behavior. Chaining is powerful, but the original object’s identity is no longer the client’s main abstraction.

From Calls to Objects

A menu item currently performs work directly:

void onUndoClicked() {
    editor.undoLastChange();
}

What if the application must queue, log, replay, or undo operations?

Command Studio

Design a command for an editor action. Show:

  • the command interface;
  • target and argument ownership;
  • execute() and, if justified, undo();
  • where command history lives; and
  • what happens when execution fails.

Compare the object design with storing a lambda. When does a named command earn its cost?

Prototype: Recognition Only

Polymorphic copying can create a fresh object when the concrete type is unknown at compile time. Prototype names this situation.

Prefer a copy constructor when the type is known. Java cloning is complex and should not be the default.

Exit Note

Decorator or Command is heavier than the problem deserves when __________.

Sequence Diagrams and Demeter

Preparation

Before class: Review Sections 6.3 and 6.9.

Entry Decision

foundations.getPile(FIRST).getCards().add(card);

Reference types: Foundations, Pile, List<Card>, and Card.

What knowledge does this client need? Which object should own “add this card to the first foundation”?

Follow the Run-Time Collaboration

Draw a sequence diagram for the message chain.

Include:

  • the initiating client;
  • each receiver;
  • return values that expose another object; and
  • the final mutation.

Then mark the dependency that makes a change to Pile or its collection ripple into the client.

Reassign Responsibility

Sketch a second interaction:

foundations.addTo(FIRST, card);

Where did the knowledge go? Did coupling disappear, or move behind a more stable interface?

Law of Demeter

A method should normally collaborate with:

  • its target object;
  • its fields;
  • its arguments;
  • objects it creates; and
  • intentionally global objects.

“One dot” is only a warning heuristic. Fluent APIs, value navigation, and collection pipelines may be acceptable. Ask whether the chain exposes unstable structure.

Compare Alternatives

  • Which participant owns the domain operation?
  • Does the revised interface merely add forwarding clutter?
  • Is the chain traversing data or directing behavior?
  • Which change would ripple through clients?
  • What does the sequence diagram reveal that a class diagram hides?

Exit Note

The chain __________ is acceptable/problematic because __________.