4.5.4 · D3Software Engineering

Worked examples — UML — use case, class, sequence, activity, state machine, component diagrams

2,512 words11 min readBack to topic

The scenario matrix

Before solving, let us enumerate every class of case. Think of this like a physics problem where you must cover all quadrants and all signs — here the "quadrants" are the different modelling choices that flip your answer.

# Case class (the "axis") The two extremes / edge you must decide between Which diagram
C1 include vs extend mandatory sub-behaviour vs optional add-on Use Case
C2 aggregation vs composition part survives the whole vs part dies with it Class
C3 multiplicity edge cases 0..1 (optional), * (many), 1..* (at least one) Class
C4 sync vs async message caller waits vs caller fires-and-forgets Sequence
C5 alt / opt / loop fragment branch, conditional, repeat inside one scenario Sequence
C6 decision vs fork choose ONE path vs run ALL paths in parallel Activity
C7 degenerate / self-transition event that does NOT change state State Machine
C8 which family & viewpoint structural vs behavioural — a real-world word problem any

Below, eight worked examples, each labelled with the cell it covers. Read the "Forecast:" line and guess before scrolling.


Example 1 — Cell C1 (include vs extend)

Step 1 — Classify each sub-behaviour as always vs sometimes. Why this step? The single deciding question for use-case factoring is: does the base always do this, or only conditionally? "Calculate tax" happens on every checkout → mandatory. "Apply coupon" happens only if a coupon exists → conditional.

Step 2 — Assign the stereotype. Why this step? Mandatory shared behaviour = <<include>>; optional conditional behaviour = <<extend>>. So Checkout <<include>> Calculate Tax, and Apply Coupon <<extend>> Checkout.

Step 3 — Get the arrow directions right. Why this step? This is the classic trap (see the parent's mistake callout). The <<include>> arrow points from base → included (Checkout depends on Calculate Tax). The <<extend>> arrow points from extension → base (Apply Coupon reaches into Checkout). See the figure — note the two arrows point opposite ways even though both are dashed.

Figure — UML — use case, class, sequence, activity, state machine, component diagrams

Verify: Delete "Apply Coupon" — does checkout still make sense? Yes (it was optional) → confirms <<extend>>. Delete "Calculate Tax" — is checkout now incomplete? Yes → confirms <<include>>. Sanity check passes.


Example 2 — Cell C2 (aggregation vs composition)

Step 1 — Apply the lifecycle test. Why this step? The parent gave us the one discriminating question: "If I delete the whole, must the part die too?" For the Playlist: delete it, songs survive → part is independent. For the Invoice: delete it, line items die → part is owned.

Step 2 — Map the answer to a diamond. Why this step? Independent part = aggregation = hollow ◇. Owned part = composition = filled ◆. So Playlist ◇— Song, Invoice ◆— LineItem.

Figure — UML — use case, class, sequence, activity, state machine, component diagrams

Verify: Cross-check with reality. A song can belong to many playlists at once — only possible if it is not owned by one → aggregation is consistent. A LineItem never belongs to two invoices → exclusive ownership → composition is consistent. ✔


Example 3 — Cell C3 (multiplicity edge cases)

Step 1 — Translate English quantities to multiplicity symbols. Why this step? Multiplicity is just a range min..max. "none or one" = . "at least one" = (the * means "unbounded"). "any number including zero" = (short for ).

Step 2 — Place the label at the FAR end. Why this step? A multiplicity written near a class says "how many of this class relate to one of the other". So Person "1" —— "0..1" Passport reads: one person, zero-or-one passport.

Figure — UML — use case, class, sequence, activity, state machine, component diagrams

Verify: For the many-to-many Student * —— * Course, read it both directions: "one student → many courses" ✔ and "one course → many students" ✔. Both ends are *, which is the fingerprint of a many-to-many → correct.


Example 4 — Cell C4 + C5 (sync vs async, and an alt fragment)

Step 1 — Classify each message. Why this step? Cell C4: a message is synchronous (solid filled arrowhead, caller blocks) if we need the answer before continuing. charge() must return success/failure before we decide what to show → synchronous. The email is fire-and-forget → asynchronous (open arrowhead).

Step 2 — Wrap the outcome in an alt fragment. Why this step? Cell C5: two mutually exclusive outcomes (success / failure) in one scenario = an alt combined fragment with two compartments guarded by [ok] and [else].

Step 3 — Order top-to-bottom. Why this step? In a sequence diagram time flows downward. charge() happens first (we wait for its return), the async email next, then the alt.

Figure — UML — use case, class, sequence, activity, state machine, component diagrams

Verify: Trace the "declined" path: charge() returns failalt picks [else] → "Declined" shown. The email was fired regardless (it is above the alt, outside it), which matches "send receipt" being independent — but wait, do we email a declined payment? The spec said fire the email after the charge call unconditionally, so the model matches the spec. (In an exam, flag this as a design smell.) ✔ structurally correct.


Example 5 — Cell C6 (decision vs fork) with a figure

Step 1 — Find the exclusive choice → decision. Why this step? "fails vs passes" are mutually exclusive — exactly one path is taken. That is a decision node ◇ with guards [fail] and [pass].

Step 2 — Find the parallel work → fork/join. Why this step? Unit tests and lint run simultaneously and are independent → a fork ━ splits into both, and a join ━ waits for both before Deploy. This is the difference the parent stressed: decision = pick one; fork = do all.

Step 3 — Order: decision first, fork inside the [pass] branch. Why this step? We only parallelise after a successful build, so the fork lives on the pass branch.

Figure — UML — use case, class, sequence, activity, state machine, component diagrams

Verify: Count exits. The decision has one active outgoing edge at runtime (either fail or pass) — correct for "choose one". The fork has two active outgoing edges at once — correct for "do all". If you accidentally used a fork for build-pass/fail you'd try to run both success and failure handling, which is nonsense. Shapes match semantics. ✔


Example 6 — Cell C7 (degenerate / self-transition) with a figure

Step 1 — List states and the "real" transition. Why this step? One object, its rest-conditions are the states. The genuine state change is Draft --publish--> Published.

Step 2 — Identify self-transitions (the degenerate cell). Why this step? A self-transition is an event that fires but does not change the state. edit in Draft: state stays Draft, an action runs. publish in Published: nothing meaningful happens → a self-loop (or you may simply omit it to say "ignored"). Both are the "zero-input" degenerate case: motion in the event-space, no motion in the state-space.

Step 3 — Decide: draw the self-loop or drop it? Why this step? Drawing it documents "this event is handled here and intentionally does nothing"; dropping it means "undefined/ignored". Being explicit is safer for exams.

Figure — UML — use case, class, sequence, activity, state machine, component diagrams

Verify: Reachability check — can we reach every state from Draft (the initial)? Draft →(publish)→ Published ✔. Are there dead ends with no way out and no final? Published has no unpublish here, so it is terminal for this spec — acceptable if that is the requirement. Self-transitions correctly leave the state count unchanged (still 2). ✔


Example 7 — Cell C8 (real-world word problem: pick the family & viewpoint)

Step 1 — Structural vs behavioural. Why this step? The parent's grand split: are we describing what exists (static shape) or what happens over time? "Deployable pieces and their dependencies" is a static packaging question → structural.

Step 2 — Choose among structural diagrams. Why this step? Structural = {Class, Component}. Class describes code-level types/attributes; Component describes deployable units and their interfaces/dependencies. "Web front end, inventory service, database" are deployable modules → Component diagram. This is really a Software Architecture question.

Step 3 — Justify the rejections. Why this step? An exam wants you to rule out the others. Sequence/Activity/State = behavioural → wrong family. Use Case answers "who does what", not "how it's packaged". Class is too fine-grained (it shows fields/methods, not deployables).

Verify: Test the choice against the client's exact words: "deployable pieces" → components ✔; "which depends on which" → dependency arrows between components ✔. The Component diagram answers both; no other diagram answers both. ✔


Example 8 — Cell C5 stress (loop fragment + degenerate empty case)

Step 1 — Choose the fragment. Why this step? "Repeatedly until a condition" = a loop combined fragment, guarded [more pages]. Cell C5 again, but the loop flavour.

Step 2 — Reason about the limiting value: zero iterations. Why this step? Always cover the degenerate input. If the guard [more pages] is false on the first check, the loop body executes zero times — the messages inside never fire. A well-drawn loop correctly represents "0 to many" repetitions, unlike a plain arrow which implies "exactly once".

Step 3 — State the loop bound. Why this step? You may annotate loop(0,*) meaning "minimum 0, maximum unbounded", making the zero-page case explicit and exam-proof.

Verify: Substitute the extreme: empty site ⇒ guard false ⇒ 0 body executions ⇒ scraper immediately finishes with no getPage() calls — matches "no more pages". Substitute many pages ⇒ body repeats per page ⇒ correct. Both limits behave. ✔


Recall

Recall Which stereotype is mandatory, and which way does its arrow point?

<<include>> ::: mandatory (always used); arrow points from base → included.

Recall One-question test for aggregation vs composition?

"If I delete the whole, must the part die?" ::: Yes → composition ◆; No → aggregation ◇.

Recall Multiplicity for "at least one" vs "any number"?

at least one ::: ; any number ::: (i.e. ).

Recall Decision ◇ vs Fork ━ — how many outgoing paths are active at runtime?

Decision ::: exactly ONE (guarded, exclusive). Fork ::: ALL (parallel).

Recall A sequence message where the caller does not wait uses which arrowhead?

Asynchronous ::: open (unfilled) arrowhead.

Recall What does a

loop fragment do when its guard is false on the first check? Runs the body ::: zero times (0-to-many, unlike a plain single arrow).