Object Design: Interactions, Classes & Code
Analysis told us what the system must do. Object design decides which software objects do it, which messages they send each other, and what their classes look like, and then turns all of that into Java. It also runs the other way: exam Q7 hands you Java and asks for the diagrams.
- You will read and draw design sequence and communication diagrams, using every notation element the exam expects.
- You will draw design class diagrams correctly: types, visibility, navigable associations with rolenames, and unambiguous arrowheads.
- You will keep the dynamic and static models consistent, and know the four kinds of visibility that let one object message another.
- You will map designs to Java and, for Q7, Java back to a DCD and a sequence diagram with concrete return values.
Where Object Design Fits
By the end of Part II you know what the system has to do: the use cases, the domain concepts, the system operations and their contracts. None of that says what the software looks like inside. Object design fills that gap. It moves us from the problem to the solution space, deciding which software objects exist, what each one is responsible for, and which messages they send each other.
Analysis, design, implementation
The lectures separate three activities. Analysis investigates the problem and the requirements; it is about making the right software, and object-oriented analysis finds and describes the concepts in the problem domain. Design is a conceptual solution that meets those requirements, and object-oriented design means "defining software objects and their collaboration". Implementation is the concrete solution: code in an OO language such as Java.
Four models: static and dynamic, analysis and design
Analysis and design each produce two kinds of model. A static model shows what exists (classes and how they relate); a dynamic model shows what happens over time (who sends which message). That gives four models, and the design pair in this Part are the software-class counterparts of the domain pair you drew in Part II.
| Static model | Dynamic model | |
|---|---|---|
| OO domain models (analysis, conceptual classes, Part II) | Domain class diagram | System sequence diagram (SSD) |
| OO design models (design, software classes, this Part) | Design class diagram (DCD) | Design sequence diagram (DSD) or communication diagram |
-, +), methods and a navigable association. The SSD treats the system as one black box; the DSD opens it and shows the objects inside.What object design starts from
Object design does not start from a blank page. Every artefact from the earlier phases feeds it something specific, and knowing which artefact gives what tells you where to look when you are stuck.
makeNewSale or enterItem(itemID, quantity), becomes the first message of one interaction diagram. In the Unified Process the result is called the Design Model: the DCDs, interaction diagrams and package diagrams together.The course concentrates on the application logic (domain) layer. Object design of that layer looks much the same whatever the technology, whereas the user-interface and persistence layers depend more on the technology chosen (see Part VIII on layers).
Three ways to go about design
Larman (ch. 14) notes that developers design in one of three ways. The course takes the middle one, with the proviso that a drawing must be worth the time it costs.
Design while coding
The design lives only in the programmer's head and the code.Sketch UML, then program
The most popular approach, and the one this course uses: draw the hard parts, then write the code.Let a tool generate it all
Draw everything and generate the code from the model. This rarely works.Check yourselfWhich of the four models (domain class diagram, SSD, DCD, DSD) would you look at to find out which methods Sale has, and which to find out in what order Sale calls them?
The DCD lists Sale's methods: it is the static design model, with method signatures but no bodies. The order of calls is dynamic behaviour, so it is in the DSD (or a communication diagram). The domain model has no methods at all, and the SSD only shows the system as a black box, so neither can answer either question.
Check yourselfYou are designing the enterItem operation and want to know what must be true when it finishes (for example, "a SalesLineItem was created"). Which earlier artefact tells you?
The operation contract for enterItem: its postconditions state exactly which objects must have been created, which associations formed and which attributes changed. Your design has to make each postcondition true. The SSD only tells you that the operation exists and what its parameters are.
Analysis finds the right problem; design invents software objects and their collaboration; implementation codes them. Each phase has a static model (class diagram) and a dynamic model (sequence diagram). Object design is fed by the use cases, domain model, SSDs, contracts and supplementary specification, and produces the Design Model.
Static vs Dynamic Design Models, and Responsibilities exam
A class diagram on its own looks like a design, but it cannot tell you how anything works. This section explains what each design model captures, why you need both, and why most of the real decisions happen in the dynamic one.
Static design model: a representation of software objects that defines class names, attributes and method signatures, but not method bodies. It is drawn as a UML class diagram, the DCD. Package diagrams are static too.
Dynamic design model: a representation of how software objects interact via messages, that is, the logic of the method bodies. It is drawn as UML interaction diagrams: sequence diagrams (DSDs) or communication diagrams. State machines and activity diagrams are also dynamic.
Why the dynamic model matters most
Drawing an interaction diagram forces you to decide exactly which message to send, to whom, and in what order. Those are the real design decisions, and they are where you apply the GRASP principles of Part IV. Larman calls this the point where "the rubber hits the road". Novices tend to think the class diagram is the important one, and Larman lists drawing only class diagrams as a common "worst practice". His guideline is to spend significant time on interaction diagrams, not just on class diagrams.
Two views of one design
The DSD and the DCD are not two separate designs; they are two views of the same design, so each one pins down part of the other. That is why the agile modelling practice is to create several models in parallel: a few minutes on an interaction diagram, a few minutes on the related class diagram, and repeat. If you are sketching on walls, use one wall for each, side by side.
st appear only on the left, while the attribute quantity and the types appear only on the right.- Every lifeline in a DSD is an instance of a class in the DCD (
lineItems[i] : SalesLineItemis an instance ofSalesLineItem). - Every message to a lifeline is an operation of that lifeline's class, with the same parameters and return type (
getSubtotal()becomes+getSubtotal() : Money). - Every message needs visibility from sender to receiver, and the DCD records it as an association (attribute visibility, as here) or a dependency (parameter, local or global visibility). See 3.8.
Workshop 3 discussion questions
These four questions from Workshop 3 are good short-answer material. Try each before opening it.
Workshop 3 · Q1What is a low representational gap?
The software's class names and structure mirror the domain, so the developer's mental model of the domain matches the code. That makes the system easier to understand and modify, and lets you trace each requirement to where it is implemented.
Workshop 3 · Q2Do we need both static and dynamic models?
Yes. In Workshop 3 Part 3, teams implemented from only a domain model and a DCD. They had to make many assumptions, and their programs ended up different from each other. The static model says what exists; only the dynamic model says how it works: what each method does, which calls it makes and in what order.
Workshop 3 · Q3Can I start designing with a dynamic model?
Yes. Larman recommends exploring with interaction diagrams first. The class definitions fall out of them (every lifeline gives a class, every message an operation; see 3.7), and then you iterate between the two.
Workshop 3 · Q4What does the dynamic model tell us about the static model?
Which methods each class needs, with which parameters and return types; which associations and navigabilities are required; which classes are missing; and whether responsibilities are badly allocated.
Practical guidance: UML as sketch
Larman's approach is agile modelling, or "UML as sketch": you model to understand and communicate, not to produce documentation, so use whiteboards and model with other people. For a three-week iteration, spend a few hours (at most a day) near the start drawing UML for the hard parts, then code, and expect the code to diverge from and improve on the sketches. If you use UML tools, prefer ones that can reverse-engineer code into both class and interaction diagrams.
Object design skill matters more than UML notation skill. What counts is knowing how to assign responsibilities (GRASP, Part IV) and apply design patterns (Part VI). UML only records those decisions.
A text-based alternative that groups use in "what-if" sessions is the CRC card (Class–Responsibility–Collaboration): one index card per class, listing its responsibilities on the left and the classes it collaborates with on the right.
Responsibilities and responsibility-driven design
How do you decide which object does what? Responsibility-driven design (RDD) treats software objects like people who have responsibilities, "the obligations or behaviours of an object in terms of its role", and who collaborate to get work done. Responsibilities come in two kinds.
Sale.time becomes the software Sale knowing its time.A responsibility is not a method. It is an abstraction, and methods fulfil it, either on their own or by collaborating with other objects. In the point-of-sale example, Sale knows its total becomes Sale.getTotal(), and Sale creates SalesLineItems becomes Sale.makeLineItem(...). To fulfil the first, getTotal collaborates with SalesLineItem.getSubtotal(), so Sale needs a collection attribute, List<SalesLineItem> lineItems, to keep attribute visibility to all its line items.
The link runs the other way as well. Drawing a message in a DSD is assigning a responsibility. If you draw :Sale sending create(cashTendered) to :Payment, you have decided that Sale is responsible for creating Payments. Choosing such assignments well is the subject of GRASP in Part IV.
Check yourselfClassify each as a knowing or doing responsibility: (a) a Sale knows its total; (b) a Register coordinates the handling of a sale; (c) a SalesLineItem knows its ProductDescription; (d) a Sale creates a Payment.
(a) Knowing: something it can derive or calculate. (b) Doing: controlling and coordinating other objects. (c) Knowing: a related object. (d) Doing: doing something itself (creating an object).
Check yourselfA classmate says "a responsibility is just another word for a public method". What is wrong with that?
A responsibility is an abstraction that one or more methods fulfil, possibly by collaborating with other objects. Sale knows its total is fulfilled by getTotal() together with every line item's getSubtotal(); it is not identical to any single method.
- The DCD (static) gives classes, attributes and signatures; the DSD (dynamic) gives the messages, i.e. the method bodies. You need both, drawn in parallel, and most real decisions are made in the DSD.
- Lifelines are classes, messages are operations, and every message needs visibility, which shows up as an association or a dependency.
- Objects have knowing and doing responsibilities; drawing a message assigns one.
Design Sequence Diagrams: the Notation exam
The design sequence diagram is the main dynamic design model, and the exam expects you to both read and draw one. If the notation is shaky you lose marks even when the design is right, so this section goes through it one element at a time. For a one-page summary, see the appendix.
SSD versus DSD
A DSD looks like the system sequence diagram from Part II, but it answers a different question. The SSD treats the system as a black box and asks "what events does the actor send it?". The DSD opens the box and asks "which software objects, sending which messages, carry out one of those events?".
| System sequence diagram (Part II) | Design sequence diagram | |
|---|---|---|
| Visualises | a use case scenario | the interactions between software objects that realise one system operation |
| System is | a black box, :System | opened up: the lifelines are design objects |
| Participants | actor(s) and :System | lifelines such as :Register, s : Sale |
| Messages | system events (system operations) and responses | method calls, each an operation of the receiver's class |
| Starting point | the actor sends the event | a found message (the system operation) arriving at the first internal object, usually the controller (Part IV) |
The usual workflow is to draw one DSD per system operation in the SSD. For Process Sale that means four diagrams: makeNewSale, enterItem, endSale and makePayment.
Lifeline boxes: who takes part
Each participant is a box at the top with a lifeline running down from it. The lifeline is dashed by convention (UML 2 also allows solid), and time runs from top to bottom along it. The text in the box says exactly which object the lifeline stands for, and there are several forms to recognise.
name : Class; either half may be missing. Name an instance (s1 : Sale) when you need to refer to it again, for example as an argument. A lifeline always represents one object, never a collection: to talk about one element, use a selector such as sales[i] : Sale.Messages
A message is a call from one object to another, drawn as a horizontal arrow from the sender's lifeline to the receiver's. Its label has a full syntax, but most of it is optional: you may leave out the type information when it is obvious, and the parentheses when there are no parameters. Java-style syntax is acceptable too.
return = message(parameter : parameterType) : returnType. Write as much of it as you need to be unambiguous.Different kinds of message, and the bar that goes with them, have different drawings. The figure shows all of them in one small interaction.
- Found message. It starts at a filled black ball, meaning the sender is unspecified or outside the design. The course notation sheet calls it the "System Trigger". In a DSD it is usually the system operation from the SSD.
- Synchronous message (a normal call): a solid line with a filled arrowhead, labelled with the message expression. The sender waits until the call returns.
- Self message: an arrow from an activation back to the same lifeline, with a nested activation bar, here
clear(). In code it isthis.clear(), often a private helper. - Asynchronous message: a solid line with an open (stick) arrowhead; the sender does not wait. Careful when reading diagrams: sketchers often use stick arrows for ordinary synchronous calls too, so don't trust the arrowhead shape alone.
- Execution specification bar (activation bar): a thin rectangle on the lifeline for as long as the method is on the call stack, its "focus of control". Bars are optional in UML, but the course diagrams use them, nested for self-calls.
- Active object: a box with double vertical lines on its sides, for an object running its own thread (more below).
Returns: two legal ways exam
A call often hands a value back, and there are two equally legal ways to show it.
d1 = getDate(). The course sheet calls this an "implicit return message". Larman prefers it when sketching because it is compact.Workshop 3 asked whether "Time = Scheduler(1)" or "a call Scheduler(1) with a dashed reply Time" is correct. Both are correct. One exam instruction changes the choice, though.
When a question asks you to show "method returns explicitly with concrete values" (Q7.3 every year), draw a dashed reply line for every call and label it with the actual value, for example -9 or "Guitar".
Creation and destruction
Objects are not all there from the start; some are made during the interaction. To show creation, send the creating message to the new object's lifeline box, drawn at the height where it is created rather than at the top. The UML idiom for the message is create(...), though new or any other name is legal. In Java a create message means new plus a constructor call.
:Sale starts lower down because it does not exist until create(). conn existed from the start and ends at the X.UML mandates a dashed line with a filled arrowhead for creation, but many authors draw it solid (as the course sheet does, with senderObject = create(sender)), and either is understood. Destruction is a «destroy» message and a large X that ends the lifeline. Java has garbage collection, so you rarely need it, but it is a way to stress that an object is no longer usable, such as a closed database connection.
Frames: control flow exam
A sequence diagram on its own is a straight line of calls. Frames (UML's "combined fragments") add loops and conditions. Each is a box around part of the diagram with an operator in the pentagon at its top-left, and each matches a Java construct.
| Operator | Meaning | Java |
|---|---|---|
loop [guard] / loop(n) | repeat while the guard is true / n times | for, while |
alt [g1] … [else] | mutually exclusive alternatives, separated by dashed horizontal lines | if / else if / else |
opt [guard] | optional fragment, executed if the guard is true | if |
par | fragments execute in parallel | threads |
region | critical region: only one thread at a time | synchronized |
sd Name | frame around a whole diagram that names it | (title) |
ref Name | interaction occurrence: inserts the named sd diagram here, spanning the same lifelines | a method call to a factored-out interaction |
public double getTotal() { // in Order
double total = 0;
for (OrderLineItem li : lineItems) {
double subtotal = li.getTotal();
total += subtotal;
}
return total;
}
[else].boolean result = service.sendMessage(...);
if (result == true) {
successResponse();
} else {
failureResponse();
}
if with no else. Frames nest: here an opt contains a loop(n).public void doOne() { // in A
if (x > 0) {
for (int i = 0; i < n; i++) {
myB.doTwo();
}
}
}
ref box stands for the whole interaction of another diagram titled sd Get order total, so a large interaction can be broken into parts. (The surrounding diagram's name, Checkout, is illustrative.)A few rules govern how frames are written. The guard is a [boolean expression], placed over the lifeline whose state it tests. Every loop needs a condition, as Tony's Pizza stresses, so write something like loop [for each line item] or loop [i < lineItems.size]. Frames can be nested. The UML 1 style for a single conditional message, [color = red] calculate, is not legal UML 2, but it is acceptable in quick sketches.
The course's notation sheet labels its frames while (messaging) and "Alternative". In the exam, use Larman's standard keywords loop and alt with bracketed guards.
Title your diagrams, which Tony's Pizza also stresses. Use an sd frame (for example sd Add pizza to order), and name a diagram whenever you want to refer to it from a ref. And how much detail should a DSD have? The Week 12 review lecture answered that it should reflect how the code works; if it gets too large, break it into parts with ref frames.
Iterating over a collection
Looping over a collection is so common that it has two standard drawings. Both mean the same Java.
[i < lineItems.size] and an action box i++ on :Sale spell out the counter. The lifeline is one selected element, lineItems[i].lineItems[i] inside a loop frame means "for each element". Because the course insists every loop has a condition, it is safest in the exam to add one, such as [for each line item].for (SalesLineItem li : lineItems) total.add(li.getSubtotal()); // either drawing
Static methods, singletons, polymorphism, async
Static (class) method calls have no instance to send to, so the message goes to the class itself: a lifeline labelled «metaclass» Calendar. Singletons are drawn as an ordinary lifeline with a 1 in the box's corner, meaning the object is reached via X.getInstance(), which is global visibility (3.8).
1. The Java under each is what the arrow means.Polymorphic messages are calls where the receiver's concrete class decides which method runs. There are three ways to draw them, depending on the source and on what the question tells you.
:Payment {abstract}, and stops there. He then draws a separate diagram for each concrete case, each starting with a found authorize message.getPrice() goes to :Item, then an alt [item instanceof Pizza] / [item instanceof Drink] frame shows each subclass's behaviour.Asynchronous calls and active objects. An asynchronous call is a stick-arrow message. An active object runs its own thread, and its box has double vertical lines on the left and right (an active class is drawn the same way in a class diagram). In Java, Thread t = new Thread(new Clock()); t.start(); invokes Clock.run() asynchronously; the Thread overhead is usually left out of the diagram.
:Main sender and its start() trigger are illustrative.The course notation reference sheet
The course's own DSD notation sheet uses a messaging back end as its example, and you should be able to recognise every element on it: the lifelines :MessagingController (unnamed) and senderObject : User (with an object name); the system trigger (found message); method invocations; the self-call processRequest(...); the object creation senderObject = create(sender); the implicit return result = saveMessage(...); the explicit return (the dashed result); and a loop frame and an alternative frame with guards. It is redrawn below with Larman's loop and alt keywords.
The sheet draws all of this as one wide diagram. To keep the text readable it is redrawn here in two parts: the controller's view, then a zoom into what the service does when its sendMessage arrives (the second part starts with that message as a found message, exactly as Larman draws a separate diagram for a sub-interaction).
processRequest and both responses are self-calls with nested bars. The dashed result is an explicit return, and the alt sits on the controller, the lifeline whose state its guards test.create messages, and senderObject is an object name that is used again as an argument later. result = saveMessage(...) is an implicit return. Long labels wrap onto two lines, which is fine on paper too.Worked example: Lucky 7 exam-style
The lecture exercise ties all this together. Use case: "Player plays a game by rolling 2 dice. System presents the outcome: win if the face values total 7, otherwise lose." In the SSD, :Player sends play to :DiceGame, which replies outcome, and the domain model has DiceGame 1 —includes— 2 Die (faceValue). Step through the lecture's DSD solution and watch each message become one line of Java.
- Three lifelines exist before
playis called: the game and its two dice. The two dice are fields of DiceGame (highlighted), which gives the game attribute visibility to both. - Found message
play() : booleanarrives at:DiceGame. It becomes the methodpublic boolean play()in class DiceGame. - The first message inside the activation,
roll()todie1, becomes the first statement:die1.roll();. fv1 = getFaceValue() : intassigns on the call, so it becomes a local variable:int fv1 = die1.getFaceValue();.- The same pair for the second die:
roll()todie2… - … and
fv2 = getFaceValue() : int, givingint fv2 = die2.getFaceValue();. - The dashed return
fv1 + fv2 == 7leaves the activation, so it becomesreturn fv1 + fv2 == 7;. The addition and comparison are internal computation, not messages. One statement per message, in top-to-bottom order.
For completeness, here is Die. Its DCD, together with DiceGame's, is in 3.5.
public class Die {
private int faceValue;
public void roll() { faceValue = (int) (Math.random() * 6) + 1; }
public int getFaceValue() { return faceValue; }
}
Check yourselfWhat object does each lifeline label denote? (a) :Register (b) lineItems[i] : SalesLineItem (c) «metaclass» Calendar (d) sales : ArrayList<Sale>
(a) An unnamed instance of Register. (b) One element, selected from the lineItems collection, of class SalesLineItem. (c) The Calendar class object itself, which receives static method calls. (d) The collection object (an ArrayList of Sales), not the sales inside it.
Spot the errorA student's DSD has a stick-figure Cashier sending enterItem to :Register, a loop frame with no guard, and a solid line labelled total going back to the caller. Name three problems.
(1) Actors don't belong in a design sequence diagram; start with a found message instead. (2) Every loop needs a guard, e.g. loop [for each line item]. (3) Returns are dashed with an open arrowhead, not solid.
Check yourselfWhich frame would you draw for each? (a) if (stock < 5) reorder(); (b) if (paid) ship(); else cancel(); (c) while (more) next(); (d) a big interaction you already drew as sd Authorise payment.
(a) opt [stock < 5]. (b) alt [paid] with a second compartment [else]. (c) loop [more]. (d) A ref frame labelled Authorise payment spanning the same lifelines.
Check yourselfWrite the Java for this DSD fragment inside Sale.makePayment(Money cash): :Sale sends create(cash) to a new p : Payment, then sends t = getTotal() to itself.
Payment p = new Payment(cash); then Money t = this.getTotal();. A create message is new plus the constructor; a self message is a call on this; t = … stores the return value in a local.
Check yourselfIn Lucky 7, suppose DiceGame held its dice in a List<Die> dice instead of two fields. How would the DSD change?
The two die lifelines become one, dice[i] : Die (a lifeline is one object, so use a selector), inside a loop [for each die] frame containing roll() and fv = getFaceValue(). The running total is internal computation in DiceGame, so it is not a message.
- One DSD per system operation, starting with a found message; lifelines are
name : Class, one object each; time runs down. - Calls: solid line, filled head. Async: stick head. Returns: dashed, open head, or
x = call(). Self-calls nest a bar. Created objects appear at creation height. - Frames
loop,alt,opt(pluspar,region,sd,ref) map to Java control flow; every loop needs a guard, and every diagram a title.
Communication Diagrams
A sequence diagram eats horizontal space: every new object needs its own column on the right. A communication diagram shows exactly the same interaction, but as an object graph: objects can go anywhere on the page, joined by links. Because position no longer shows the order of messages, sequence numbers do instead.
The notation
The elements are few, and most of them are about how the numbers are written:
- A link is a plain line between two objects. It is an instance of an association, and it means navigation (visibility) exists. All messages, in both directions, flow along one link, rather than one line per message.
- A message is written
seq: message(args), with a small arrow beside the link showing its direction. - The first (starting) message is not numbered. Numbering it is legal, but Larman's guideline is not to, because it complicates all the numbers below it.
- Nesting is shown by prepending the number of the incoming message:
1: msg2,1.1: msg3,2: msg4,2.1: msg5,2.2: msg6. - A self message travels along a link that loops back to the same object, for example
1: clear. - Creation is a message such as
1: create(cashier)to the new object. Optionally tag the object{new}, or, if the message has a non-obvious name, stereotype it:«create» 1: make(cashier). - A conditional message is written
1 [color = red] : calculate. Mutually exclusive paths are lettered, as in1a [test1] : msg2and1b [not test1] : msg4, and their nested messages become1a.1and1b.1. - Iteration is written
1 * [i = 1..n] : num = nextInt, or just*. Over a collection, send1 *: st = getSubtotaltolineItems[i] : SalesLineItem. - Static calls, polymorphism and async work as in sequence diagrams: a
«metaclass»box, separate diagrams for each polymorphic case, and a stick arrow for async.
The same interaction, both ways
The best way to see that the two diagrams are equivalent is to draw one interaction twice. Here is Larman's NextGen enterItem. The sequence diagram shows order by position; the communication diagram shows it by the numbers 1, 2, 2.1, 2.2.
create and add, which sit inside makeLineItem's activation on :Sale, become 2.1 and 2.2. The unnumbered enterItem is the starting message.The course's communication reference sheet
The course has a communication-diagram version of the messaging back end too. Its numbering, though, differs from Larman's: it numbers the found message (1*, the * marking a looping operation) and uses flat numbers 2, 3, 4, … for the service's calls. (Its 7.1b guard is printed as true, clearly a typo for false.) Use Larman's nested scheme if asked. Applied to this example it gives the diagram below.
result.| Message | Course sheet | Larman (use this) |
|---|---|---|
found sendMessage(messageRequest) | 1* | unnumbered |
processRequest | 2 | 1 |
controller → service sendMessage | 3 | 2 |
getUser(sender), create(sender) | 4, 4.1 | 2.1, 2.1.1 |
getUser(contact), create(contact) | 5, 5.1 | 2.2, 2.2.1 |
create(message, …), setTimeStamp | 6, 6.1 | 2.3, 2.3.1 |
result = saveMessage(...) | 7 | 2.4 |
successResponse / failureResponse | 7.1a / 7.1b | 3a / 3b |
Sequence or communication?
The two kinds of interaction diagram trade off against each other: sequence diagrams win on reading the order, communication diagrams on space.
| Type | Strengths | Weaknesses |
|---|---|---|
| Sequence | clearly shows time ordering (read top to bottom); richest notation (frames etc.); best tool support; good for documentation and reverse-engineered call flows | new objects must go on the right, so it consumes horizontal space |
| Communication | space-economical: objects can go anywhere, so wall sketches are easy to modify and it fits narrow pages | harder to see the sequence (you must read the numbers); fewer notation options |
Check yourselfIn a sequence diagram, :Order receives checkout() (found). Inside it, Order sends getTotal() to :Cart, which sends getPrice() to :Item; then Order sends create(total) to a new :Invoice. Number these for a communication diagram.
checkout() is unnumbered. Then 1: getTotal() (Order → Cart), 1.1: getPrice() (Cart → Item, made while Cart handles message 1), and 2: create(total) (Order → Invoice, tagged {new}).
Spot the errorA communication diagram has two separate lines between :A and :B, one per message, and numbers its first, found message 1, then continues 2, 3 for calls made inside it. What would Larman change?
Use one link between A and B; all messages, both directions, travel along it. Leave the starting message unnumbered, and nest the numbers of calls made while handling a message (1, 1.1, 1.2 …) rather than numbering flat.
Check yourselfYou must fit an interaction with nine objects onto a narrow whiteboard and expect to rearrange it often. Which diagram, and what do you give up?
A communication diagram: objects can be placed anywhere, so it is space-economical and easy to modify. You give up the at-a-glance time ordering (readers must follow the numbers) and the richer frame notation of sequence diagrams.
A communication diagram is a sequence diagram redrawn as an object graph. Links are plain lines carrying every message; order comes from numbers, with the first message unnumbered and nested calls numbered n.1, n.2. Letters (1a, 1b) mark alternatives, * iteration, {new} creation. Sequence diagrams show time better; communication diagrams save space.
Design Class Diagrams: the Notation exam
The DCD is the static half of the design: the blueprint a programmer types the class definitions from. Every Q7 starts with one, and markers check its notation strictly, so this section covers every element you may need to draw or read.
Design class diagram (DCD): a UML class diagram drawn from a software (design) perspective. It shows software classes and interfaces with their attributes (with types), operations (signatures), associations with navigability, and generalisation, realisation and dependency relationships. It is part of the UP Design Model.
Classifier: "a model element that describes behavioral and structural features". Classes, interfaces, use cases and actors are all classifiers.
The class box
A class box has three standard compartments: name, attributes, operations. You may add user-defined compartments, such as "exceptions thrown" or "responsibilities". An ellipsis "…" means "more elements, not shown", whereas a blank compartment conventionally means "no members".
Demo is made up; its members are the guide's own examples gathered together. Read each line against its callout.The two syntaxes to memorise are these:
- Attribute:
visibility name : type multiplicity = default {property-string}, e.g.- quantity : Integer,middleName : String [0..1](may be null),- MAX : Integer = 6 {readOnly}, and/total(derived). - Operation:
visibility name(parameter-list) : return-type {property-string}, e.g.+ getPlayer(name : String) : Player {exception IOException}. UML 2 officially dropped the return type, but Larman says to assume the version with a return type. Java syntax is also allowed if you tell the reader.
The visibility markers map directly onto Java's access modifiers.
| Marker | Visibility | Java |
|---|---|---|
+ | public | public |
- | private | private |
# | protected | protected |
~ | package | (no modifier) |
When no marker is shown, the defaults are that attributes are private and operations public. As Tony's Pizza puts it, "in design, attributes are generally private and methods are generally public". Several more conventions are worth knowing:
- Return types are types, not values. Write
+pay() : boolean, never+pay() : success, and writevoidwhen nothing is returned. - Static (class-level) attributes and operations are underlined, e.g.
getInstance(). - An abstract class or operation has an italic name, or
{abstract}, which is handier when drawing by hand. The course sheet writes«Abstract»above the class name, which is understood. {leaf}means final (cannot be overridden) and{readOnly}means constant.{ordered}and{unique}are properties of a collection, and user-defined property strings such as{List}are allowed.- Constructors are shown as
«constructor» SalesLineItem(desc : ProductDescription, qty : Integer), but they are often omitted because they follow fromcreatemessages. Getters and setters are usually omitted too: they are noise. - A method body can be shown in a UML note stereotyped
«method», although this mixes dynamic detail into a static diagram.
A UML operation is a declaration: name, parameters, return type, exceptions and constraints (operation contracts are constraints on operations). A method is the implementation of an operation.
Attributes: text or association line? exam
A field whose type is another class can be drawn two ways, and both mean exactly the same Java field. The choice is about readability. Larman's guideline is to show data types as attribute text and attributes of other (non-data-type) classes as association lines, which make the connection visible. Data types are things whose identity doesn't matter: Boolean, Date/DateTime, Number/int, String, Time, Address, Color, PhoneNumber, ZIP code and enumerations.
id as text (a data type), and currentSale and location as navigable associations whose rolenames are the field names.- Draw a navigability arrow (open arrowhead) from the owning class to the target, the attribute's type.
- Put the multiplicity at the target end only.
- Put a rolename at the target end, and make it the field name, optionally with visibility (
-currentSale). - Give it no association name. Association names belong to domain models, which in turn have no navigation arrows.
Collections work the same way: either text with a multiplicity, or a line to the element class with the multiplicity at its end and, optionally, {ordered, List}.
lineItems.The course's DCD notation sheet still writes association names ("Holds", "Takes Out", "Uses") on DCD lines. Larman says they are legal but usually not useful in a DCD; rolenames matter more, because they map to field names.
Relationship lines exam
Workshop 11 warns that markers "will not be guessing or inferring" whether an arrowed line means navigation or inheritance. Both the line style (solid or dashed) and the arrowhead (open, hollow triangle, diamond) must be right. Here is every relationship with its Java meaning.
Interfaces
An interface is a class box with the keyword «interface» above the name. It holds operations only, since Java interfaces have no instance fields, and those operations are public. An implementing class is joined to it by a realisation: a dashed line with a hollow triangle, from the class to the interface. Two shorthands also exist: lollipop notation (a circle on a stick: the class provides the interface) and UML 2 socket notation (a half-circle: the class requires it). A plain dependency line pointing at an interface also shows that a class requires it.
Generalisation and abstract classes
In a DCD, generalisation means OO-language inheritance; in a domain model it only meant subset/superset of concepts. In a subclass box, list an inherited operation only if the subclass overrides or implements it; Larman's polymorphism figure, for instance, lists authorize() in both CreditPayment and DebitPayment. Exam questions may override this convention (see 3.11), so read the instruction.
{abstract}) marks Payment as abstract; both subclasses repeat authorize() because each overrides it.Dependency exam
Dependency: a client element has knowledge of a supplier element, so a change in the supplier could affect the client. Drawn as a dashed arrow from client to supplier.
One class can depend on another in several ways: it may have an attribute of the supplier's type; send a message to a supplier (via an attribute, parameter, local, global or static method); receive a parameter of the supplier's type; or the supplier may be its superclass or an interface it implements. Attributes, superclasses and interfaces already have their own lines (association, generalisation, realisation), so don't add a dependency line on top of those.
Use the dependency line for parameter, local-variable, global and static-method dependencies.
public class Sale {
public void updatePriceFor(ProductDescription d) {
Money p = d.getPrice(); // parameter use
// ...
}
}
public class Foo {
public void doX() {
System.runFinalization(); // static call
}
}
The course sheet's LoanService ⇢ Customer «Uses» and LoanService ⇢ Account «Uses» illustrate the same guideline: createLoan(customer : Customer) takes a Customer parameter and then gets that customer's Account into a local variable.
Composition versus aggregation
Composition (filled ◆) is a strong whole–part relationship with three properties: (1) a part belongs to only one composite at a time; (2) a part must always belong to a composite; and (3) the composite is responsible for creating and deleting its parts, so if the composite is destroyed, so are its parts. Larman's guideline is to use it when appropriate, as in Board ◆ Square, Sale ◆ SalesLineItem and Hand ◆ Finger, and not to name it, since it is implicitly "has-part". Tony's Pizza uses it for Order ◆ OrderLineItem and OrderLineItem ◆ Pizza.
Aggregation (hollow ◇) is a vague "has-a". UML gives it no distinct semantics, and Rumbaugh calls it a "modeling placebo", so Larman's guideline is don't bother using aggregation: use a plain association or composition. The course does use it, though: Tony's Pizza has Pizza ◇ PizzaStyleDescription and Pizza ◇ Topping, because a style or topping "has a life of its own", and the course sheet has LoanService ◇ Loan.
The slides and case study use aggregation, while the textbook advises against it. Either is accepted if your reasoning about shared versus exclusive lifetime is sound, and when unsure, a plain navigable association is never wrong.
Other DCD notation
The remaining notation is less central but can turn up in a question.
itemID reduces the target multiplicity (usually * becomes 1), which is why it maps to a Map. The singleton's 1 and the enumeration both reappear in Part VI's patterns.- A constraint is text in braces, inside the box or in a note, possibly in OCL:
size : Integer {size >= 0},{post condition: new size = old size + 1}. - A qualified association has a small qualifier box on the source end holding the key:
ProductCatalog [itemID] → 1 ProductDescriptionis aMap<ItemID, ProductDescription>. - An association class is a class box joined by a dashed line to the association:
Company *—Employs—* PersonwithEmployment {salary, startDate}. - A singleton class has a
1in the top-right of the name compartment, and an underlinedinstanceandgetInstance().ServicesFactory(Part VI) is one. - A template (generic) class has its parameter in a dashed box on the corner, bound as
ArrayList<T→Square>or Java-style,squares : List<Square>. - An enumeration has the
«enumeration»keyword with its literals listed;State {Open, Locked, Closed}becomes anenum. - An active class has double vertical lines on its sides: it runs its own thread (
implements Runnable). - A keyword is a textual adornment categorising an element, such as
«interface»,«actor»,{abstract}or{ordered}; when sketching,<interface>or<I>are fine. A stereotype is a refinement of a modelling concept defined in a UML profile, shown in guillemets, and may declare tags, as in«authorship» author = "craig". - A property string has the form
{name1=value1, name2}, e.g.{abstract, visibility=public}. A package-qualified name isjava.awt::Font(official) orjava.awt.Font.
The course DCD reference: Online Banking
The course's DCD notation sheet uses an online banking system. You should be able to recognise and reproduce each element on it: an enumeration, two abstract classes, all three common visibility markers, a static attribute and method (underlined), a derived attribute, generalisations, navigable associations with multiplicities, a composition, an aggregation, two «Uses» dependencies, and an interface with two implementations.
# id : Int (protected), - balance : Double (private), + withdraw(…) (public); the underlined static state : State and updateState(); the derived / repaymentPeriod; Customer ◆ Address (composition); Loan Service ◇ * Loan (aggregation); the dashed «Uses» dependencies to Customer and Account; and Variable Rate and Fixed Rate realising the interface, each listing calculate(). The sheet's association names (Holds, Takes Out, Is In, Uses) are its course style.Lucky 7's DCD
The lecture's solution gives DiceGame the attributes - die1 : Die and - die2 : Die and the operation + play() : boolean, and gives Die - faceValue : int, + getFaceValue() : int and + roll() : void, joined by the association "includes" with multiplicity 1 → 2. The slide shows the dice both as attribute text and as an association line, which Larman calls legal but redundant.
Spot the errorFind four problems in this DCD fragment: Order with attributes Name, $cost and customer : Customer, an operation +pay() : success, and also a line to Customer labelled "places" with no arrowhead.
(1) Name has no type or visibility: write - name : String. (2) $cost is not UML: write - cost : double. (3) : success is a value, not a type: write +pay() : boolean. (4) customer appears twice (text and line), and the line is domain-model style: give it a navigability arrow towards Customer, rolename customer and multiplicity 1 at the target, drop "places", and remove the attribute text.
Check yourselfWhich line do you draw? (a) class Cat extends Animal (b) class Cat implements Pet (c) Cat has a field private Owner owner; (d) Cat.feed(Food f) calls f.eat() but keeps no field.
(a) Solid line, hollow triangle at Animal. (b) Dashed line, hollow triangle at Pet. (c) Solid line, open arrowhead at Owner, rolename -owner and multiplicity 1 (or 0..1 if it may be null) at the Owner end. (d) Dashed line, open arrowhead at Food: a dependency (parameter visibility).
Check yourselfShould - createdAt : DateTime and - owner : Customer be text or lines, by Larman's guideline?
createdAt is a data type, so it stays as attribute text. owner is another class, so draw a navigable association to Customer with rolename -owner and multiplicity at the Customer end.
Check yourselfA Board creates its 40 Squares and they never belong to anything else; a Library lists Books that can move to another library. Composition, aggregation or plain association?
Board ◆ Square is composition: exclusive parts created and destroyed with the whole. Library and Book share no lifetime and the book can move, so a plain navigable association is safest; the course would also accept aggregation, which Larman advises against.
- Class box: name /
vis name : Type/vis op(p : T) : R. Defaults: attributes private, operations public. Underline = static; italic or{abstract}= abstract. - Data types as text; references to other classes as navigable associations with the rolename = field name and multiplicity at the target, and never both.
- Solid+triangle = extends; dashed+triangle = implements; dashed+open = dependency (parameter, local, global, static); diamonds at the whole. Make every arrowhead unambiguous.
From Domain Model to DCD exam
You rarely draw a DCD from nothing: it grows out of the domain model, which keeps the representational gap low. But almost every element changes on the way from the conceptual perspective to the software one, and mixing the two styles is one of the commonest ways to lose marks. The row to remember is operations: that is the main difference.
| Aspect | Domain model (conceptual, Part II) | Design class diagram (software) |
|---|---|---|
| Purpose | understand the problem domain; shared vocabulary | blueprint for the code |
| Classes | real-world conceptual classes only; no software artefacts (no DB, UI, controllers, collections) | software classes, including new ones with no domain counterpart: controllers, Pure Fabrications, line-item classes, interfaces, enums, adapters, factories |
| Attributes | name only (type optional); no visibility | visibility name : Type (usually private) |
| Operations | none | full signatures: visibility, parameter names and types, return type. The main difference. |
| Associations | association name (verb phrase) with reading direction; multiplicity at both ends; no navigability | navigability arrows; rolename = field name and multiplicity at the target end; association names usually omitted |
| Generalisation | subset/superset of concepts | OO-language inheritance |
| Interfaces, realisation, dependency | not used | used |
| Collections | implicit in the multiplicity | an explicit collection type (List, Map) in code; {ordered, List} on the end |
| Composition / aggregation | conceptual whole–part | lifetime and ownership in software |
Workshop 3 illustration. The domain model has Subject (name, subjectCode) 0..4 —taking— 0..* Student (name, studentNumber). The equivalent design classes add types and visibility and gain operations, here + quit(subject : Subject) : void.
The procedure (Tony's Pizza, week 3a)
- Pick the classes the current use case needs. For add pizza to order: Order, Pizza, PizzaStyleDescription and Topping. Keep the domain names, so the representational gap stays low.
- Add visibility and types to the attributes (generally private), and drop attributes this use case doesn't need; Topping's stock level, for example, belongs to inventory.
- Firm up loose attributes. A fixed set of options becomes an
«enumeration»(PizzaSize). A key-to-value lookup becomes a Map (pricing : Map<PizzaSize, Double>). - Add classes the domain didn't have, such as an OrderLineItem to hold a quantity on the Order–Pizza relationship: the "line item" pattern for putting attributes on an association.
- For each association decide its navigability (who holds the reference), its rolename (the field name), its multiplicity at the target, and whether it is composition or aggregation. A many-multiplicity means a collection field, an ArrayList in Java.
- Add methods (responsibilities), such as
Order.addPizza(style, size)andOrder.getTotal() : double. Use DSDs to discover and check them. - Walk a scenario through the classes (computing the order total, say) to check every needed method and link exists. That walk is a DSD.
- The starting point: a simplified domain model for the use case. Conceptual classes, plain attribute names, named associations with multiplicities at both ends, no methods, no arrows.
- Step 1, pick the classes (highlighted) the use case "add pizza to order" needs, and keep their names.
- Step 2, visibility and types. The boxes become software classes with typed, private attributes (
-orderNumber : String).stockLevelis dropped because it belongs to inventory, and Pizza's loose attributes are flagged for the next two steps. - Step 3, firm up loose attributes. The fixed set of sizes becomes the
«enumeration» PizzaSize, and the price lookup becomes-pricing : Map<PizzaSize, Double>. - Step 4, a new class.
quantitydescribes the pairing of an order with a pizza, so it moves to a new OrderLineItem, which has no domain counterpart. - Step 5, associations. Each gets a navigability arrow, a rolename that will be the field (
-lineItems,-item,-style…), a target multiplicity and a diamond where the course uses one: composition for Order ◆ OrderLineItem and OrderLineItem ◆ Pizza, aggregation for the style and toppings, which "have a life of their own". Pizza keepsaddedToppingsseparate from the style'sincludedToppings. - Step 6, methods. Operations with full signatures appear:
addPizza(style : PizzaStyleDescription, size : PizzaSize) : OrderLineItem(abbreviated in the box),getTotal(),getPrice(),getPrice(size),getCostOfAddOn(),editQuantity(qty). - Step 7, walk a scenario. Computing the order total follows the highlighted path: Order → each OrderLineItem → its Pizza → the style's price for the size, plus each added topping's cost. Every hop has a method and a navigable link, so the design can do it. That walk is the "Get order total" DSD below.
Two details of this DCD are worth noticing. Pizza keeps addedToppings apart from its style's includedToppings because the price is the base price for the size plus the cost of each added topping. And strictly, enum literals are shared values, so composition (exclusive ownership) to PizzaSize is questionable; Tony's Pizza draws the enum as a composition line, but Larman would write -size : PizzaSize as attribute text, since enums are data types.
Workshop 3, Part 1 Q2: "what assumptions did you make, and where would you get that data?" Typical assumptions concern data types, visibility, which class owns which responsibility, navigability and collection types. The data can come from the use case text and other requirements, the supplementary specification, the glossary, stakeholders and domain experts, existing systems or code, and technical constraints.
Worked example: the matching DSDs
Here are the two Tony's Pizza DSDs that must agree with the DCD above. One detail shows the iterate-until-consistent loop of 3.7 in action: the DCD video first declared addPizza(...) : void, but the DSD video returns the new line item, so the DCD was updated to return OrderLineItem. Message names, parameters and return types must match the DCD.
create(myPizza, 1) passes the new pizza and a quantity of 1 to the line item's constructor.chosenStyle and toppings are at the top; every loop has a guard; and the arithmetic (price × quantity, the summing) is not drawn as messages, because it is internal computation.Check yourselfA student's DCD has an association "contains" with multiplicities at both ends and no arrowheads, attributes without types, and no operations. What has gone wrong?
It is still a domain model. A DCD needs typed attributes with visibility, operations with signatures, and navigable associations with a rolename (the field name) and multiplicity at the target end; association names are usually dropped.
Apply itIn a library domain model, Member —borrows— Book has an attribute dueDate that belongs to the pairing of a member and a book. What does step 4 tell you to do in the DCD?
Introduce a new class (a "line item" such as Loan) to hold - dueDate : Date, and connect Member and Book through it, exactly as OrderLineItem holds quantity between Order and Pizza.
Check yourselfA pizza style has a different price for each size. How should that appear in the DCD, and why not three attributes?
As a key-to-value lookup: -pricing : Map<PizzaSize, Double>, keyed by the «enumeration» PizzaSize. Three separate price attributes would hard-code the set of sizes and need changing whenever a size is added.
Keep the domain names, then add types and visibility, turn fixed options into enums and lookups into Maps, add software-only classes (line items), decide navigability, rolenames, multiplicities and diamonds, add methods, and walk a scenario to check it. The biggest single difference between a domain model and a DCD is that the DCD has operations.
Keeping the DSD and DCD Consistent exam
Because the DSD and DCD are two views of one design, markers check that they agree. The Workshop 3 partial solution requires that across all diagrams methods belong to the same classes, with the same parameters and result types, and the rubrics for both projects reward design models that are "consistent with the code". This checklist catches almost every inconsistency.
- Every lifeline's class appears in the DCD.
- Every message to a lifeline is an operation of that receiver's class (or one it inherits), with matching parameters and return type.
- Every message implies visibility from sender to receiver. In the DCD it must be an association (attribute visibility, navigable toward the receiver) or a dependency (parameter, local, global or static).
- Every
createmessage corresponds to a constructor, which can be shown as«constructor». - The SSD's system operation is the DSD's starting message, and an operation of the receiving (controller) class.
- Iterate: changing one diagram usually forces a change in the other.
- A DSD for
makePayment(top) and the DCD it implies (bottom). Step through the checklist one item at a time. - Check 1, classes. Every lifeline (
:Register,:Sale,:Payment) has a class box in the DCD. - Check 2, operations. The message
makePayment(cashTendered)to:Salemust be an operation of Sale, with the same parameter:+makePayment(cashTendered : Money). - Check 3, visibility. Register sends Sale a message, so Register must see Sale. It does through attribute visibility: the navigable association
currentSale, multiplicity 1, pointing at Sale. - Check 4, creation.
create(cashTendered)to:Paymentcorresponds to the constructor«constructor» Payment(cashTendered : Money). - Check 5, the starting message. The found
makePayment(cashTendered)is the SSD's system operation, so it must be an operation of the controller,Register. - Check 6, iterate. If you now add a message to the DSD (say Sale asks itself for its total), add the operation to Sale; if you rename an operation in the DCD, rename the message. Repeat until both views agree.
Spot the inconsistencyThe DSD shows :Register sending total = getTotal() to :Sale. The DCD has Sale with +calculateTotal() : Money, and Register has no association to Sale. List the problems.
(1) The message name doesn't match an operation of the receiver's class: rename one so both say getTotal() : Money. (2) Register has no visibility to Sale: add a navigable association Register → Sale (e.g. rolename currentSale, multiplicity 1), or justify a dependency if Sale is a parameter or local.
Check yourselfWhy does a create(desc, qty) message to sl : SalesLineItem say something about SalesLineItem's class definition?
It implies a constructor SalesLineItem(desc : ProductDescription, qty : Integer), which may be shown in the DCD as «constructor». In Java it is new SalesLineItem(desc, qty).
Lifeline ⇒ class; message ⇒ operation of the receiver with the same signature; message ⇒ visibility (association or dependency); create ⇒ constructor; system operation ⇒ controller operation. When one diagram changes, update the other.
Visibility Between Objects exam
Every arrow you draw in a DSD hides a practical question: how does the sender actually get hold of the receiver? An object can only call a method on something it has a reference to. That is what visibility is about, and getting it right is what keeps the DSD, the DCD and the code consistent.
Visibility is "the ability of one object to see or have reference to another". For A to send a message to B, B must be visible to A.
This is not the UML visibility markers + - # ~. Those control access to members; visibility here is about whether A holds a reference to B at all.
The four kinds
There are four ways for A to see B. They differ in how long they last, how common they are, and how they appear in the DCD. In this section each kind keeps its own colour: attribute, parameter, local and global.
| Kind | Definition | Permanence | How common | In the DCD |
|---|---|---|---|---|
| Attribute | B is an attribute (field) of A | relatively permanent: as long as A and B exist | most common | association A → B |
| Parameter | B is a parameter of a method of A | temporary: only within the method | 2nd | dependency A ⇢ B |
| Local | B is a non-parameter local variable in a method of A, created with new or assigned from a call's return value | temporary | 3rd | dependency A ⇢ B |
| Global | B is globally visible (a global variable; in Java a static field or a Singleton) | relatively permanent | least common | dependency A ⇢ B |
Each tab shows one kind as a DSD fragment, the DCD line it implies, and the Java.
public class Register {
private ProductCatalog catalog; // ATTRIBUTE
private Sale currentSale; // ATTRIBUTE
public void enterItem(ItemID id, int qty) {
ProductDescription desc =
catalog.getProductDescription(id);
currentSale.makeLineItem(desc, qty);
}
}
public class Sale {
private List<SalesLineItem> lineItems = new ArrayList<>();
// PARAMETER: Sale -> ProductDescription in this method only
public void makeLineItem(ProductDescription desc, int qty) {
lineItems.add(new SalesLineItem(desc, qty)); // LOCAL (implicit)
}
public void updatePriceFor(ProductDescription d) {
Money p = d.getPrice();
}
}
public void enterItem(ItemID id, int qty) {
// LOCAL: assign the returned object to a local
ProductDescription desc =
catalog.getProductDescription(id);
currentSale.makeLineItem(desc, qty);
}
1 on its lifeline.public void makePayment(Money cashTendered) {
currentSale.makePayment(cashTendered);
// GLOBAL via Singleton (preferred way)
IAccountingAdapter acc = ServicesFactory
.getInstance().getAccountingAdapter();
// ...
}
Turning temporary visibility into permanent
Parameter and local visibility are commonly turned into attribute visibility. The typical case is a constructor storing its parameter in a field, as SalesLineItem does (Larman Fig. 19.4).
public class SalesLineItem {
private ProductDescription description;
private int quantity;
public SalesLineItem(ProductDescription desc, int qty) {
description = desc; // PARAMETER -> ATTRIBUTE
quantity = qty;
}
}
Two more details. There is implicit local visibility: in anObject.getFoo().doBar(); the returned Foo is a temporary local with no name. And global visibility through a true global variable is possible in C++ but not in Java; the Singleton pattern is the preferred way to get it (Part VI), marked by a 1 on the lifeline.
The design consequence
When drawing a DSD, ask for each message how the sender sees the receiver. If it can't, give it visibility, then record the choice in the DCD.
Check yourselfName the kind of visibility A has to B in each: (a) void m(B b) { b.f(); } (b) private B b; then b.f(); (c) B b = factory.makeB(); b.f(); (d) B.getInstance().f();
(a) Parameter. (b) Attribute. (c) Local (assigned from a call's return value). (d) Global, through a Singleton.
Check yourselfWhich of those four produce an association in the DCD, and which a dependency?
Only (b), attribute visibility, is an association A → B (with rolename b). Parameter, local and global visibility are each drawn as a dashed dependency A ⇢ B.
Apply itYour DSD has :Order sending charge(amount) to :PaymentGateway, but Order has no field, parameter or local of that type. Give two ways to fix the design.
Any of: add a field gateway : PaymentGateway to Order (attribute visibility, drawn as an association); pass the gateway in as a parameter of the method that charges (parameter visibility); obtain it from an earlier call's return value into a local; or reach it as a Singleton, PaymentGateway.getInstance() (global). Then show the choice in the DCD.
To message B, A must see B. Attribute visibility (a field) is permanent and drawn as an association; parameter, local and global (Singleton) visibility are dependencies. Parameter visibility is often turned into attribute visibility by storing it in a field. For every DSD message, decide how the sender sees the receiver, and record it in the DCD.
Mapping Designs to Code exam
Once the two design views agree, writing the code is largely a mechanical translation: class and interface definitions come from the DCD, and method definitions come from the interaction diagrams. Larman adds a warning: expect lots of change while programming, because the diagrams are an incomplete first step.
DCD → class definitions
Each DCD element has a direct Java counterpart. Start with a single class and its association.
public class SalesLineItem {
private int quantity; // -quantity : Integer
private ProductDescription description;// association, role description
public SalesLineItem(ProductDescription desc, int qty) {
description = desc; // «constructor» / create(desc, qty)
quantity = qty;
}
public Money getSubtotal() { // +getSubtotal() : Money
return description.getPrice().times(quantity);
}
}
Associations become fields, and the multiplicity and any qualifier decide the field's type.
class Register {
private Sale currentSale; // 0..1: may be null
}
class Sale { // declare by interface, instantiate concrete
private List<SalesLineItem> lineItems = new ArrayList<>();
}
class ProductCatalog {
private Map<ItemID, ProductDesc> descriptions
= new HashMap<>();
}
class Student { private Subject subject; } // both sides,
class Subject { private Student student; } // kept consistent
Types and inheritance map just as directly.
{abstract} = abstract, «enumeration» = enum.public interface IExpr { int eval(); }
public class Constant implements IExpr {
private int value;
public int eval() { return value; }
}
public abstract class Payment { /* ... */ }
public class CashPayment extends Payment { }
public enum State { Open, Locked, Closed }
// underlined + {readOnly} with a default:
public static final int MAX = 6;
The full list of rules, for reference:
| DCD element | Java |
|---|---|
class Sale; «interface» IExpr; {abstract} or italic | public class Sale; public interface IExpr; abstract class |
generalisation Sub ——▷ Super; realisation Impl - - ▷ I | class Sub extends Super; class Impl implements I |
attribute - quantity : Integer | private int quantity; |
underlined (static) attribute; {readOnly} with a default | static; a final constant, e.g. public static final int MAX = 6; |
derived attribute /total | usually no field; a computed getTotal() |
navigable association A → 1 B, rolename b | field in A: private B b; (0..1: may be null) |
multiplicity * / 1..*, or {ordered, List} | private List<B> bs = new ArrayList<>(); (declare by interface, instantiate a concrete class) |
qualified association with key k : K | private Map<K, B> bs = new HashMap<>(); |
| association without arrows (bidirectional) | a field on both sides, kept consistent |
operation + getSubtotal() : Money | public Money getSubtotal() { ... } |
create(desc, qty) message or «constructor» | constructor SalesLineItem(ProductDescription desc, int qty) |
dependency A ⇢ B | no field: B appears only as a parameter, local variable or static call in A's methods |
«enumeration» | enum |
| composition | the whole creates (or exclusively receives) and holds its parts, and does not share them |
Associations to code (Workshop 4 review)
The Workshop 4 review showed how each kind of association turns into fields, including how multiplicity bounds get enforced.
// directed: only Student has a field
class Student { Subject subj; }
// undirected 1-1: both, set up via constructors
public Subject(Student student) { ... }
// 0..4 <-> 0..*: a collection on each side
class Student {
static final int MAX_SUBJ = 4;
private Collection<Subject> subjects;
public boolean addSubject(Subject subject) {
if (subjects.size() >= MAX_SUBJ) return false;
...
}
public boolean removeSubject(Subject subject) { ... }
}
class Subject { private Collection<Student> students; }
The upper bound is enforced inside the add method (MAX_SUBJ = 4), while a lower bound of 1 is typically enforced by requiring the object in the constructor.
Collection classes. One-to-many relationships need a collection object (a List, a Map, an array), chosen by requirement: a Map for key lookup, a List for a growing ordered list. Larman's guideline (20.5) is to declare a variable by its interface type, not its concrete class: private List<SalesLineItem> lineItems = new ArrayList<>();. Exceptions: consider the large-scale exception strategy during design, because it has architectural impact; UML shows exceptions in property strings, e.g. {exception IOException}.
Interaction diagram → method bodies
Each message sent within a method's activation in the interaction diagram becomes one statement in that Java method, in the same order.
Read top to bottom, then, the interaction diagram gives you the body of each method in the receiving class:
- An incoming message
m(p)to:Cbecomes a methodm(P p)defined in class C. - Each outgoing message
n(a)tob : B, in top-to-bottom order, becomesb.n(a);, so the sender needs visibility tob. r = n(a), or a reply line labelledr, becomesR r = b.n(a);.create(a)sent to a newx : XbecomesX x = new X(a);.- A
loop [guard]becomes afor (...), and iteration overitems[i]becomesfor (Item i : items).alt [g] / [else]becomesif (g) {...} else {...}, andopt [g]becomesif (g) {...}. - A self message
clear()becomesthis.clear();, often a private helper. A message to«metaclass» Calendarbecomes the static callCalendar.getAvailableLocales();. - A reply arrow leaving the method's activation becomes
return value;.
doOne's activation, two statements in doOne.public class A {
private B myB = new B(); // attribute visibility
public void doOne() {
myB.doTwo();
myB.doThree();
}
}
Larman's NextGen enterItem is a bigger example, here as a communication diagram. Messages 1 and 2 are sent while Register handles enterItem, so they form Register's method; 2.1 and 2.2 are sent while Sale handles message 2, so they form makeLineItem.
// in Register
public void enterItem(ItemID id, int qty) {
ProductDescription desc =
catalog.getProductDescription(id); // 1
currentSale.makeLineItem(desc, qty); // 2
}
// in Sale
public void makeLineItem(ProductDescription desc,
int qty) {
lineItems.add(new SalesLineItem(desc, qty));
// 2.1 create, 2.2 add
}
Order of implementation exam
Implement (and ideally unit-test) classes from least-coupled to most-coupled.
Then each class depends only on classes that already exist and have been tested, so every step compiles and can be tested in isolation. In NextGen the order is (1) Payment, (2) ProductDescription, (3) ProductCatalog, (4) SalesLineItem, (5) Sale, (6) Register, (7) Store.
Plus, Times, UMinus and Constant, then UnaryExpr and BinaryExpr.Test-driven development and refactoring (Larman ch. 21)
Is it examinable? Possibly. In the Week 12 review lecture the lecturer was asked whether TDD and refactoring would be in the exam and answered possibly, because both are part of the workshop supplementary materials. So know the basics.
Test-driven development (TDD), or test-first development, is an XP practice that also suits the UP. You write the unit test before the class it tests, imagining as you write it that the code already exists. The rhythm is a little test code, then a little production code to make it pass, then more test code, and so on; you do not write all the tests first. It fits the least-to-most-coupled order naturally.
Every test method has three steps: create the fixture (the object under test), do something to it (call the method being tested), and check the results with assertions. To create Sale.makeLineItem, you first write:
public class SaleTest extends TestCase { // JUnit 3 style, as in Larman
public void testMakeLineItem() { // idiom: testDoFoo tests doFoo
Sale fixture = new Sale(); // 1. create the fixture
ProductDescription desc =
new ProductDescription(new ItemID(1), new Money(2.5), "product 1");
fixture.makeLineItem(desc, 1); // 2. exercise the method under test
fixture.makeLineItem(desc, 2);
assertTrue(fixture.getTotal().equals(new Money(7.5))); // 3. check the result
}
}
Only then do you write makeLineItem to make the test pass. Larman gives five advantages: the unit tests actually get written; writing the test first is more satisfying, so programmers test more consistently; writing the test clarifies the detailed interface and behaviour of the method (one of TDD's major benefits in practice); the growing suite gives provable, repeatable, automated verification; and it gives you the confidence to change code.
A structured, disciplined method to rewrite or restructure existing code without changing its external behaviour, applying small transformation steps combined with re-executing tests at each step.
That is why it pairs with TDD: after each small step the tests prove nothing regressed, and many such steps can add up to a major design improvement. Its goals are those of good programming: remove duplicate code, improve clarity, shorten long methods, remove hard-coded literal constants. Code smells hint that the design may be poor: duplicated code, a big method, a class with many instance variables, a class with lots of code, strikingly similar subclasses, little or no use of interfaces, and high coupling between many objects. Refactorings have names, like patterns; Larman's samples are Extract Method (move part of a long method into a private helper), Extract Constant (replace a literal with a named constant), Introduce Explaining Variable (put an expression's result into a well-named temporary) and Replace Constructor Call with Factory Method (replace new with a helper that hides the details).
public void takeTurn() {
int rollTotal = 0;
for (Die d : dice) { d.roll(); rollTotal += d.getFaceValue(); }
Square newLoc = board.getSquare(piece.getLocation(), rollTotal);
piece.setLocation(newLoc);
}
public void takeTurn() {
int rollTotal = rollDice(); // call to the extracted helper
Square newLoc = board.getSquare(piece.getLocation(), rollTotal);
piece.setLocation(newLoc);
}
private int rollDice() { // created by Extract Method
int rollTotal = 0;
for (Die d : dice) { d.roll(); rollTotal += d.getFaceValue(); }
return rollTotal;
}
The dice-rolling loop in Monopoly's Player.takeTurn() is a distinct, cohesive unit of behaviour, so moving it into a helper makes takeTurn shorter and clearer and supports High Cohesion. (The "before" version is simply the helper inlined back.) Larman adds that the helper is only a stepping stone: in iteration 2, Pure Fabrication replaces it with a Cup class (Part IV).
Check yourselfWrite the Java field for each DCD end: (a) Order → 1..* LineItem, rolename items, {ordered}; (b) Library [isbn] → 1 Book, rolename books; (c) Car → 0..1 Driver, rolename driver.
(a) private List<LineItem> items = new ArrayList<>(); (b) private Map<ISBN, Book> books = new HashMap<>(); (c) private Driver driver;, which may be null.
Check yourselfA DSD shows :Cart receiving checkout(), then t = getTotal() to itself, then create(t) to a new inv : Invoice, then a dashed reply inv. Write Cart.checkout().
public Invoice checkout() {
Money t = this.getTotal();
Invoice inv = new Invoice(t);
return inv;
}Check yourselfWhy implement from least- to most-coupled, and which NextGen class comes last?
So that each class depends only on classes that already exist and are tested; every step then compiles and can be unit-tested in isolation. Store comes last (7), because it depends on Register, which depends on Sale, and so on.
Check yourselfWhat three steps does every TDD test method contain, and what makes a change a "refactoring"?
Create the fixture; exercise the method under test; check the result with assertions. A refactoring restructures code without changing its external behaviour, in small steps, re-running the tests after each one.
- DCD → class definitions: attributes become fields, navigable associations become fields named by the rolename (List or Map for many or qualified ends), triangles become
extends/implements, dependencies become no field at all. - Interaction diagram → method bodies: one statement per message, in order;
r = m()is an assignment, create isnew, frames arefor/if, a reply isreturn. - Implement least-coupled first; TDD writes the test first; refactoring changes structure, not behaviour.
Code → Diagrams: the Exam Q7 Procedure exam
Exam Q7 runs the mapping of 3.9 backwards: you are given Java and asked to draw its DCD, and then a DSD for a given call, with concrete values. Both are mechanical if you follow a fixed procedure, and both are worth a lot of marks. The running example is the 2021 Semester 1 calculator; its full code is in 3.11, and the parts you need are shown with each stepper.
Procedure A: Java code → design class diagram
- One box per type. Interfaces get
«interface», abstract classes an italic name or{abstract}, enums«enumeration». - Fields with primitive or data types (
int,double,boolean,String, dates) go in the attribute compartment as text, e.g.- value : int. - Fields whose type is another class or interface in the code become an association line: solid, open arrowhead at the field's type. At the target end put the rolename (field name with visibility,
-left) and the multiplicity:1for a reference always set by the constructor,0..1if it may be null,*(or0..*) for collections and arrays. One line per field:private IExpr left, right;is two associations. Don't repeat the field as text when the question asks for associations. implementsbecomes a dashed line with a hollow triangle at the interface;extendsa solid line with a hollow triangle at the superclass.- Operations are written
visibility name(param : Type, ...) : ReturnType. Interface operations are public (+). Constructors use the class name, optionally with«constructor». Package-private members (no Java modifier) get~. - Inherited/implemented methods: obey the question. "Omit methods which can be inferred to exist through inheritance" (2021 S1) means don't repeat them in implementing classes; "Include all methods which exist through inheritance" (2021 S2) means list them in every implementing class.
- Dependencies. For each parameter type, local variable type or static call target that is not already an association or supertype, draw a dashed open-arrow dependency.
- Static members are underlined,
finalconstants get{readOnly}, and generic collections may carry{ordered, List}. - Check: every field appears exactly once, every arrowhead is unambiguous, every association has a rolename and multiplicity, and every attribute, parameter and return has a type.
public interface IBinaryOp { int apply(int left, int right); }
class Plus implements IBinaryOp { public int apply(int left, int right) { return left + right; } }
class Times implements IBinaryOp { public int apply(int left, int right) { return left * right; } }
public interface IUnaryOp { int apply(int value); }
class UMinus implements IUnaryOp { public int apply(int value) { return -value; } }
public interface IExpr { int eval(); }
public class BinaryExpr implements IExpr {
private IExpr left, right;
private IBinaryOp op;
BinaryExpr(IExpr l, IBinaryOp o, IExpr r) { left = l; op = o; right = r; }
public int eval() { return op.apply(left.eval(), right.eval()); }
}
public class UnaryExpr implements IExpr {
private IExpr expr;
private IUnaryOp op;
UnaryExpr(IUnaryOp o, IExpr e) { op = o; expr = e; }
public int eval() { return op.apply(expr.eval()); }
}
public class Constant implements IExpr {
private int value;
Constant(int v) { value = v; }
public int eval() { return value; }
}
- Read the code once, listing every type: three
interfaces and six classes. - Step 1, one box per type.
IExpr,IBinaryOpandIUnaryOpget«interface»; the six classes get plain boxes. - Step 2, data-type fields as text. Only
Constanthas one:private int valuebecomes-value : int. Anintcan never be an association. - Step 3, class-typed fields as associations, one line per field.
private IExpr left, right;gives two lines,-leftand-right;opgives a third. UnaryExpr'sexprandopgive two more. Each has multiplicity1, because the constructor always sets it. - Step 4,
implements→ dashed realisations. Six of them, each with a hollow triangle at the interface (shared triangles keep it tidy). - Step 5, operations. Interface operations are public with typed parameters, e.g.
+apply(left : int, right : int) : int. The constructors have no Java modifier, so they are package-visible,~. (Constructors are optional; the official answer omits them.) - Step 6, obey the inheritance instruction. S1 says omit methods inferable through inheritance, so
eval()andapply(...)are not repeated in the implementing classes. - Step 7, dependencies. Does any method use a class type other than through a field or supertype? No: every call goes through
left,right,exprorop. So no dependency lines are needed. - Step 8, check. Every field once (left, right, op, expr, op, value); all arrowheads unambiguous; every association has a rolename and multiplicity; types everywhere. Note the two associations from BinaryExpr back to IExpr: that recursive structure is the hallmark of Composite.
Procedure B: Java code + initialisation → DSD with concrete values
- From the initialisation code, list the object graph. Label each object
varName : ConcreteClass, using the runtime class rather than the declared interface. Objects created inline withneware anonymous, e.g.:Plus. - The objects already exist, so put all lifelines at the top and don't draw creation. Order them left to right by first involvement.
- Start with a found message (filled ball) to the receiver, e.g.
eval(). - Trace the code exactly, in Java evaluation order: arguments are evaluated left to right, before the call they are passed to. For each call draw a solid filled-arrow message
method(args)(with concrete argument values where known) and an activation bar on the receiver. - For every call, draw a dashed return arrow labelled with the concrete value, then continue with the caller.
- Don't draw operators (
+,*, unary-, string concatenation) or field reads as messages: they happen inside the activation. - End with the return to the found-message source (the course draws it back to a ball), and give the diagram a title (
sd expr.eval()).
expr.eval() for −9 + 2- Steps 1–2, the object graph. The initialisation creates
nine,uexpr(holding an anonymous:UMinus),twoandexpr(holding an anonymous:Plus). All six lifelines go at the top, labelled with runtime classes, and no creation is drawn. - Step 3, the found message
eval()reachesexpr : BinaryExpr, which runsreturn op.apply(left.eval(), right.eval());. - Java evaluates the arguments of
applyfirst, left to right. So the first call isleft.eval(), andleftisuexpr. It runsreturn op.apply(expr.eval());. - Again the argument comes first:
nine.eval()returns its field, so draw the dashed return9. - Now
uexprcan callapply(9)on:UMinus, which returns-9. The unary minus is an operator insideapply, not a message. uexpr.eval()is finished: dashed return-9toexpr. Its whole evaluation completes before the right operand starts.- Second argument:
right.eval(), i.e.two.eval(), returns2. - With both operands known,
exprcallsapply(-9, 2)on:Plus, which returns-7. The+inside is not a message. - Step 7, finish.
exprreturns-7to the caller. Every call has a dashed return with its concrete value, and the activations are nested. Add the titlesd expr.eval()(shown in 3.11).
Check yourselfA class has private Node next; private int key; private List<Node> children;. What goes in the DCD, assuming the question asks for associations wherever possible?
-key : int as attribute text. Two separate navigable associations from the class to Node: rolename -next with multiplicity 0..1 (or 1 if always set by the constructor), and rolename -children with multiplicity * (optionally {ordered, List}). Neither is repeated as text.
Apply itUsing the calculator, give the order of messages and returns for expr.eval() where expr = new BinaryExpr(new Constant(3), new Times(), new Constant(4)), and the final value.
Found eval() to expr : BinaryExpr; eval() to the first :Constant, return 3; eval() to the second :Constant, return 4; apply(3, 4) to :Times, return 12; expr returns 12. The constants were created inline, so they are anonymous lifelines (two separate :Constant lifelines). No creation is drawn.
Spot the errorA student's Q7.3 answer labels the lifelines expr : IExpr and :IBinaryOp, draws apply(-9, 2) before two.eval(), and draws a message - for the unary minus. Fix it.
Use the runtime classes: expr : BinaryExpr, :Plus. Arguments are evaluated before the call, so two.eval() (returning 2) must come before apply(-9, 2). Operators are internal computation, so remove the - message; the negation happens inside UMinus.apply.
A: one box per type; data-type fields as text; every class-typed field as its own navigable association with rolename and multiplicity; dashed or solid triangles for implements and extends; typed operations; obey the inheritance instruction; dependencies for other uses; check. B: lifelines from the object graph with runtime classes, all at the top; found message; trace in Java evaluation order (arguments first, left to right); a dashed concrete return for every call; no operators; title it.
Past Exam Q7: Worked Answers exam
Q7 is worth 23 of 120 marks in both 2021 exams. It has three parts: a DCD from code, the GoF patterns in that code, and a DSD with concrete returns. Both papers are worked in full below, each with the official answer's differences noted.
DCD from code
Procedure A. Watch the instruction about inherited methods.Name the patterns
Composite and Strategy both years. Name every participant, and find both Strategy instances.DSD with concrete values
Procedure B. Dashed return with a value for every call.2021 Semester 1: Calculator
The code is the calculator shown in 3.10.
Q7.1 (10 marks). Draw the DCD: "as complete as possible; show Java attributes as UML associations wherever possible; omit methods which can be inferred to exist through inheritance or similar relationships."
-value : int as text. eval() and apply are not repeated in the implementing classes, as instructed.- Three interfaces, marked
«interface», with+eval() : int,+apply(left : int, right : int) : intand+apply(value : int) : int. - Six classes. Plus, Times and UMinus have no attributes and (per the instruction) no repeated
apply; likewise the Expr classes don't repeateval(). - Six realisations (dashed, hollow triangle at the interface): Plus and Times to IBinaryOp, UMinus to IUnaryOp, BinaryExpr, UnaryExpr and Constant to IExpr.
- Five navigable associations, each with a rolename and multiplicity
1at the target:-leftand-right(separate lines) and-opfrom BinaryExpr;-exprand-opfrom UnaryExpr. - Constant keeps
-value : intas text: anintcan't be an association. - Constructors may appear with typed parameters and package visibility
~. Constructors aren't inherited, so the "omit" instruction doesn't remove them, but the official answer leaves them out altogether, so they are optional, not mark-earning. - No composition diamonds: the sub-expressions are created outside and passed in, so "the composite creates its parts" isn't evidenced; a plain navigable association is accurate.
- No dependency lines: no method uses a class type except through fields.
The official answer (released 26/27 Oct) matches this in every structural point: the three interface boxes, Constant with - value : int, six dashed realisations, and five navigable associations with rolenames -expr, -left, -right and -op (twice), each with 1 at the interface end. Three details of its notation are worth copying: it puts * at the source end of every association (many expressions can share one sub-expression or operator object); it writes parameters as types only (apply(int, int)), which is enough; and it draws no constructors.
* at the source end, and type-only parameters.Q7.2 (5 marks): the patterns (details in Part VI and Part VII).
- Composite: the component is
IExpr, the composites areBinaryExprandUnaryExpr(they hold IExpr children), and the leaf isConstant. - Strategy: the strategy interfaces are
IBinaryOpandIUnaryOp, the concrete strategiesPlus,TimesandUMinus, and the contextsBinaryExprandUnaryExpr, whose behaviour is swappable viaop.
The official answer gives the same two patterns and shows the marks split: 2 for Composite and 3 for Strategy. For Composite it says that UnaryExpr, Constant and BinaryExpr share the interface IExpr and that Constant is the only atomic instance; for Strategy it says IUnaryOp and IBinaryOp are the interfaces of two different instances of the pattern. Spotting both Strategy instances is where the third mark comes from.
Q7.3 (8 marks). Given nine = new Constant(9), uexpr = new UnaryExpr(new UMinus(), nine), two = new Constant(2) and expr = new BinaryExpr(uexpr, new Plus(), two) (that is, −9 + 2), draw the DSD for expr.eval() showing returns explicitly with concrete values. The trace: BinaryExpr.eval evaluates left.eval() first, which is uexpr: it calls nine.eval() (9) then UMinus.apply(9) (−9), and returns −9. Next right.eval(): two returns 2. Finally Plus.apply(-9, 2) returns −7, which expr returns.
var = message() : Type style. (The official diagram's call from expr to two has no label, a slip; write eval().)- Six correctly labelled lifelines (
name : ConcreteClass, with:UMinusand:Plusanonymous), and a found message to start. - The right order: uexpr's evaluation completes before
two.eval(), andapplycomes after both operands. Activations nested. - Arguments shown, as concrete values (
apply(9),apply(-9, 2)) or as the variables that hold them. - Dashed returns labelled
9,-9(from apply),-9(from uexpr.eval),2,-7(from apply) and-7(final). - No creation messages, and no message for the unary minus.
The official answer has the same six lifelines in the same order, the same found message (eval() : int) and the same call order. Its one notational difference is binding returns to names (e = 9, apply(e) : int, l = -9, r = 2, v = apply(l, r) : int returning -7). Either style is fine as long as every return shows its concrete value.
2021 Semester 2: Musical Director's lineup
interface ISound { String play(); }
class Duet implements ISound {
private ISound melodyInstrument, backingInstrument;
private IDuetMusic music;
Duet(ISound mi, ISound bi, IDuetMusic m) { melodyInstrument = mi; backingInstrument = bi; music = m; }
public String play() { return music.playMusic(melodyInstrument, backingInstrument); }
}
class Soloist implements ISound {
private ISound instrument;
private ISoloistMusic music;
Soloist(ISoloistMusic m, ISound i) { music = m; instrument = i; }
public String play() { return music.playMusic(instrument); }
}
class Instrument implements ISound {
private String type; // e.g. "Guitar", "Drums", "Vocals"
Instrument(String t) { type = t; }
public String play() { return type; }
}
interface IDuetMusic { String playMusic(ISound melodyInstr, ISound backingInstr); }
class Jazz implements IDuetMusic {
public String playMusic(ISound melodyInstr, ISound backingInstr) {
return "Jazz. " + melodyInstr.play() + " melody, " + backingInstr.play() + " backing.";
}
}
class Rock implements IDuetMusic {
public String playMusic(ISound melodyInstr, ISound backingInstr) {
return "Rock. " + melodyInstr.play() + " melody, " + backingInstr.play() + " backing.";
}
}
interface ISoloistMusic { String playMusic(ISound instrument); }
class Blues implements ISoloistMusic {
public String playMusic(ISound instr) { return "Blues. " + instr.play() + " soloist."; }
}
Q7.1 (12 marks). Draw the DCD: "as complete as possible; show Java attributes as UML associations wherever possible; include all methods which exist through inheritance or similar relationships." This is the opposite of S1's instruction.
play or playMusic, as instructed. The long playMusic signatures wrap onto two lines. The three dashed arrows into ISound are the parameter dependencies of Jazz, Rock and Blues.- Three interfaces with their operations; six classes, each listing its implemented method (
+play() : Stringin Duet, Soloist, Instrument;+playMusic(...) : Stringin Jazz, Rock, Blues). - Six realisations.
- Five navigable associations with rolenames and multiplicity 1: Duet → ISound as
-melodyInstrumentand as-backingInstrument(two lines), Duet → IDuetMusic-music, Soloist → ISound-instrument, Soloist → ISoloistMusic-music. Instrument's-type : Stringstays as text (String is a data type); constructors are package-visible (~).- Dependencies Jazz ⇢ ISound, Rock ⇢ ISound and Blues ⇢ ISound, because they receive ISound parameters and send them
play(). Adding IDuetMusic ⇢ ISound and ISoloistMusic ⇢ ISound is also defensible, since their signatures mention ISound.
The official answer has the same nine boxes and six realisations, with the methods repeated exactly as instructed. It is sparser in four ways, none of which makes the model answer wrong: it draws Duet's two instrument fields as one association with multiplicity 2; its associations carry multiplicities but no rolenames (rolenames are still worth writing, since the procedure asks for them and they cost nothing); it draws no constructors, no dependency lines and, probably by oversight, no - type : String (keep it: the question says "as complete as possible"); and it names the soloist interface ISoloMusic, a slip for ISoloistMusic.
Q7.2: the patterns.
- Composite: the component is
ISound, the compositesDuetandSoloist(they contain ISound children), and the leafInstrument. - Strategy:
IDuetMusicwith concrete strategiesJazzandRock(contextDuet), andISoloistMusicwithBlues(contextSoloist).
The official answer calls Composite "Composition" (the same thing). For Composite there is one mark for each class you say implements ISound, so naming Soloist, Instrument and Duet is worth three marks; it adds that Instrument is the only atomic instance. For Strategy, ISoloistMusic and IDuetMusic are interfaces of two different instances, and you get at most 1 mark if you identify only one. Lesson for every "list the patterns" question: name each participating class explicitly, and look for a second instance of the same pattern.
Q7.3 (6 marks). Given guitar = new Instrument("Guitar"), saxophone = new Instrument("Saxophone") and duet = new Duet(guitar, saxophone, new Jazz()), draw the DSD for duet.play() with concrete returns.
duet.play()- Four objects exist:
duet(declared ISound, but its runtime class is Duet), an anonymous:Jazz, and the two instruments. All at the top, no creation. - The found message
play()reachesduet : Duet, which runsreturn music.playMusic(melodyInstrument, backingInstrument);. - The arguments are field reads, not messages, so the first call is
playMusic(guitar, saxophone)to:Jazz(writing the parameter names is also fine). - Jazz builds
"Jazz. " + melodyInstr.play() + …left to right, soguitar.play()comes first, returning"Guitar". - Then
saxophone.play(), returning"Saxophone". The concatenation is not drawn. - Jazz returns the whole string
"Jazz. Guitar melody, Saxophone backing."to duet … - … and duet returns the same string to the caller. Four dashed returns, each with its exact value.
duetlabelled with its runtime classDuet, although it is declared ISound;:Jazzanonymous.playMusicpassed the two instrument objects (playMusic(melodyInstrument, backingInstrument)is also fine).- Jazz calls
play()on the melody (guitar) before the backing (saxophone), following the left-to-right concatenation. - Four dashed returns with exact strings:
"Guitar","Saxophone", then the full sentence twice. - String concatenation not drawn as messages.
The official answer has the same four lifelines and call order: a found play() : String, then playMusic(Instrument, Instrument) : String (argument types rather than names, acceptable), then the two play() calls and the combined string returned twice, using decimal message numbers (1.1, 1.2, …). Don't copy its three slips: its final string reads "Classical. Guitar melody, Saxophone backing.", but the exam's Jazz.playMusic returns "Jazz. " + …, so "Jazz. Guitar melody, Saxophone backing." is correct; its numbering starts the found message at 2 and the inner call at 1, which is out of order; and it labels the strategy lifeline just Jazz, which reads as a class name, so write :Jazz.
Check yourselfIn S2, what is the DSD for soloist.play() where soloist = new Soloist(new Blues(), guitar)?
Lifelines soloist : Soloist, :Blues, guitar : Instrument. Found play() to soloist; playMusic(guitar) to :Blues; play() to guitar, return "Guitar"; Blues returns "Blues. Guitar soloist."; soloist returns the same string. No creation, no concatenation messages.
Check yourselfWhy does S1's model DCD repeat no methods in the implementing classes, while S2's repeats them all?
Because the instructions are opposite: S1 says "omit methods which can be inferred to exist through inheritance", S2 says "include all methods which exist through inheritance". Always obey the question's instruction over the default convention.
Check yourselfA student lists only "Composite and Strategy (IDuetMusic)" for S2 Q7.2. Which marks do they lose?
By the published scheme, Composite marks are one per class named as implementing ISound, so not naming Duet, Soloist and Instrument loses those; and identifying only one Strategy instance caps Strategy at 1 mark. They should also name ISoloistMusic with Blues (context Soloist).
Both 2021 Q7s are a Composite (an interface with composite classes holding children of that interface, and one leaf) plus two Strategy instances (operator or music interfaces). Draw every field as its own role-named association, obey the inherited-methods instruction, and in the DSD use runtime classes, evaluation order and a dashed concrete return for every call.
Common Mistakes and the Marker's Checklist exam
These mistakes come from the Workshop 3, 4 and 11 review slides, the Workshop 3 partial solution, the Workshop 11 instructions and the rubrics. Read them as a checklist to run over every diagram before you hand it in.
$; (3) field shown twice; (4) a value instead of a return type; (5) domain-model association name, no arrow, no rolename; (6) ambiguous arrowhead: navigation or inheritance?Design class diagrams
- Don't leave attributes without types or visibility (
Name), or use non-UML symbols ($cost): write- name : Stringand- cost : double. The Workshop 3 partial solution's checklist covers visibility modifiers, return types, UML syntax, consistency with the domain model, and appropriate allocation of responsibilities. - Don't write a value instead of a return type (
+pay() : success): write+pay() : boolean. - Give every association multiplicities, rolenames and a clear navigability arrow, pointing from the class holding the field.
- Avoid ambiguous arrowheads; markers will not guess. Generalisation and realisation use a hollow triangle; navigation and dependency an open stick arrowhead; realisation and dependency are dashed.
- Don't show the same field twice (text and line) when asked for associations, and don't draw primitives (
int,String) as association lines. - Don't miss one of two fields declared together (
left, right). Two role-named lines is clearest and is what the official S1 answer does; the official S2 answer merged two fields into one association with multiplicity2, so a single line is accepted when its multiplicity counts both. - Don't ignore the instruction about inherited methods: omit them in S1-style questions, include them in S2-style ones.
- Don't forget the interface operations, and don't put fields into
«interface»boxes. Constructors are optional: neither official 2021 answer draws them, though including them does no harm. - Don't leave dependency lines missing for parameter, local or static use, or add them redundantly where an association already exists.
- Avoid domain-model habits in a DCD (no operations, no types, association names instead of rolenames, no navigability), and the reverse, design habits in a domain model (types, methods, software classes; see Part II).
Design sequence diagrams
- Don't draw a return as a solid line, or leave it unlabelled: returns are dashed with an open arrowhead.
- Don't put actor stick figures or
:Systemin a design SD; start with a found message. - Label lifelines
name : Classor:Class. With concrete objects, use the runtime class, not the declared interface. Don't let one lifeline silently stand for a whole collection (useitems[i] : Itemin a loop). The one accepted shortcut, from the Workshop 11 partial solution (Part IX), is a lifeline named for the collection, such aszoneList : ZoneUnit, receiving messages prefixed with*and a note saying "*denotes that the function is run on all elements". - Don't send messages to a class that has no such method in the DCD, or with parameters and return types that differ from it.
- Don't draw a loop or alt frame without a guard: every loop needs a condition, and alt compartments need guards or
[else]. - Draw created objects at their creation height, not at the top; conversely, don't draw creation for objects the question says already exist.
- Follow Java's left-to-right argument evaluation, or the call order will be wrong, and don't leave out nested activations.
- Don't forget the diagram title or
sdframe.
General
- Avoid inconsistency between the SSD, DSD, DCD and code. The Workshop 3 partial solution checks it, and so does the "design models are consistent with the code" rubric criterion.
- Exam diagrams are hand-drawn: use pencil and eraser, draw arrowheads deliberately, keep the layout uncluttered, and state any assumptions (Workshop 11). The Workshop 11 partial solution adds that your models may differ from the official ones depending on your assumptions, and its DCD shows the expected habit of justifying each difference from the domain model in a note.
Slides versus textbook
In the exam, follow Larman, but recognise the forms the course uses.
| Topic | Course (slides, sheets, case study) | Larman (use in the exam) |
|---|---|---|
| Aggregation | used (Tony's Pizza, LoanService ◇ Loan) | discouraged: plain association or composition |
| DCD association names | written on lines ("Holds", "Takes Out") | rolenames only |
| Abstract classes | «Abstract» | italics or {abstract} |
| Frame labels | "while", "Alternative" | loop, alt with guards |
| Lucky 7 DCD | attributes and association both shown | one or the other (redundant) |
| Enums | drawn as an association line (Tony's Pizza) | attribute text (enums are data types) |
| Communication numbering | first message numbered; flat numbers | first unnumbered; nested numbers |
| Creation arrows | solid | dashed per the UML spec (either understood) |
- DCD boxes: every attribute has visibility and a type; every operation has typed parameters and a return type; interfaces marked and holding operations only.
- DCD lines: every field exactly once; associations navigable with rolename and multiplicity; triangles for extends/implements, dashed where needed; dependencies for parameter/local/static use.
- Instruction: inherited methods omitted or included as the question says.
- DSD: title; found message; runtime classes; correct evaluation order; nested activations; every frame guarded; created objects at creation height (or none if they already exist); a dashed, labelled return wherever asked.
- Consistency: every message an operation of its receiver, with visibility shown in the DCD.
Spot the errorA Q7.1 answer draws BinaryExpr with one line to IExpr labelled -left, and lists -right : IExpr and -value : int as attribute text in BinaryExpr. What is wrong?
right should be its own association (the question asks for associations wherever possible), and value belongs to Constant, not BinaryExpr. Each of left and right is a separate role-named line with multiplicity 1.
Spot the errorA DCD shows Sale ⇢ SalesLineItem as a dependency and a navigable association lineItems between the same two classes. Is that right?
No: the association already records the stronger relationship (a field), so the dependency line is redundant. Dependencies are for parameter, local, global and static use that is not already an association or supertype.
Most lost marks are notation slips: untyped attributes, values as return types, missing rolenames or arrows, ambiguous arrowheads, duplicated fields, solid returns, unguarded frames, actors in a DSD, created objects at the top, and inconsistency between diagrams. Follow Larman in the exam, recognise the course's variants, and run the checklist.