May 23, 2011, 9:28 p.m.
posted by xinger
You place the messages used to perform the collaboration on the basic diagram of the participants. Each message, which is a communication between a sender object and a receiver object, is indicated on a line connecting the two of them.
The whole diagram is enclosed in a frame and you use the abbreviation sd to stand for your communication diagram.
Warning You may be wondering why the abbreviation for a communication diagram is sd and not cd. We’ve wondered about that, too—and we’ve complained. Looks like this must have been one of those silly compromises that got made when the UML gurus got too tired. They wanted all the interaction diagrams to have the same abbreviation—to simplify things. And they didn’t want to use id or int because they thought those would be confusing. That’s why we have to live with sd as the abbreviation for sequence diagram, communication diagram, timing diagram, and interaction-overview diagram. The gurus can always justify using sd by saying that a communication diagram is a type of sequence diagram. With any luck, an early revision to UML 2 may yet fix it. In the meantime, if all that ambiguity bothers you, you may want to use cd as your abbreviation for communication diagram (provided your UML tool allows it).
The name of the communication diagram is the name of the use case or operation that you are diagramming. Because you are typically doing design when you make a communication diagram, you should consider taking a more formal approach to documenting the arguments and return values of the interaction. In Figure, we name the interaction based on the use-case name GenerateBill(rmNum:RoomNumber, out newBill:Bill).
With this as a name, you indicate that the GenerateBill interaction takes a RoomNumber as input argument—and that inside the interaction, this argument is called rmNum. There is also an output argument (of type Bill) that will be called newBill inside the interaction. Normally, if you create an object inside an interaction and it has to be visible outside, you also indicate it as an out argument or a return.
Message syntax on a communication diagram is essentially the same as for the sequence diagram. (You can find more information on this syntax in Chapter 12.) The first key difference you notice is that on your communication diagram, the messages are numbered—and each message is executed in sequential order. By examining Figure, you can see that the following steps are executed in this order:
thisRoom=getRoom(rmNum): First, the GenerateBill controller asks the HotelInventory container class to find the correct Room object with the given rmNum. The correct object is returned and placed in an attribute within the GenerateBill controller named thisRoom. The HotelInventory object can find the correct Room because this relationship is indexed/qualified by roomNumber (See the Figure).
occFlag=isOccupied(today): Next, the GenerateBill controller queries the Room to see if it isOccupied(today). The GenerateBill controller can send the message to the room because the query is called on the Room object that is was returned from call #1.The notation thisRoom at the end of the message line reminds you of the way the GenerateBill controller knows about the object. The results from the query are returned and stored in an occFlag (short for occupationFlag), which is a local attribute of the GenerateBill controller.
This is a good example of how designing the messages can cause structural changes to the class diagram. Because the GenerateBill now knows about a Room object, we may decide that there is a link between the two objects. We cover this and other approaches in Figure.
[occFlag] newBill = Bill(thisRoom, controller=self): Next, the GenerateBill controller queries the Bill and tells it how to find the room by passing it the thisRoom argument. The controller has this value because call #1 returns it. But, before the call can be initiated, the controller checks the guard condition [occFlag], which was returned from call #2. If the call is performed, the Bill object is returned as newBill, which matches the return argument of the interaction.
The controller also creates a reference to itself and passes that to the Bill. This reference will be used in the next call (call #4) so the Bill can find the controller again. Self is a reserved keyword, representing the calling or executing object.
billReady(self): Lastly, the Bill object calls the billReady() operation on the GenerateBill controller and passes a reference to itself back to the controller. The Bill is able to find the controller because the controller was passed in call # 3.
Communication diagrams give you the numbering capabilities to display graphically the calls to operations—and then the calls from those called operations, and (in turn) the calls from the operations they call, and so on. If you can keep your head from spinning, you can identify as many levels of calls and operations as you need (or at least as many as will fit on the diagram).
This miracle is done by using a tool you’ve seen if you’ve ever examined a table of contents: an outline-numbering scheme. If an object gets a message to execute an operation that is numbered 3:, any messages it then issues (numbered 3.1:, 3.2:, or 3.3:) are subordinate messages because they’re issued within the context of 3:. Accordingly, any message starting 3.x: must complete its business before the top 3: message can be considered complete. This follows the traditional outline numbering pattern shown below:
3: 3.1: 3.2: 3.2.1: 3.2.2: 3.3: 3.4: 4:
In Figure, we use some outline numbering of the messages. Examine (for example) message 2, where the GenerateBill controller asks the Room object if it isOccupied. To accomplish this work, the Room object also calls an operation on another object; in this case, it calls an operation on the latest Stay object ([latest]:Stay). Because this operation is subordinate, it needs a lower-level outline number. You would use 2.1, because this is the first (and only in this case) subordinate operation within operation 2. In the example, this operation on the Stay returns an occFlag to the Room if the latest stay included today. The Room, in turn, returns the occFlag back to the GenerateBill controller. When you use this outline-style technique of numbering, you can detail how each operation works and calculate results for its caller.
Message 3: also has some subordinate steps:
3[occFlag]: newBill = Bill(thisRoom, controller=self) 3.1: thisStay = getStay(today) 3.2: party = getParty 3.3: getDayRange(Bill.sd = sd : startDate, Bill.ed = ed : endDate) 3.4: getTotalCharges(sd, ed) 3.4.1*: getLodgingCharge()
This sequence of messages is governed by the guard condition on message 3. If the [occflag] is false, the whole sequence beginning with 3 is skipped. If [occFlag] is true, then message 3 is sent to create the Bill. Then (as the diagram says), the Bill sends message 3.1 to the Room and follows up with message 3.2, 3.3, and 3.4 to the Stay. As any number of levels can be used, message 3.4.1* getLodgingCharge() is sent by the Stay to the Lodging.
In Figure, you may see that there is a message with an * in the sequence number, 3.4.1*: getLodgingCharge(). This * indicates that many instances of that message are sent with that same number. We recommend thinking of this * as a multiplicity indicator, similar to that used on UML associations. If there’s just a *, it indicates that the message is to be repeated as often as needed. If you repeat a message, then you also repeat all its subordinate messages.
If you want to have the message repeated a specific number of times, the syntax is as follows:
The iteration clause has several common forms:
Boolean expression: The expression repeats as long as the expression is True. A message such as 3*[isMoreNeeded] would continue until isMoreNeeded=False.
loopVariable=lowerLimit..upperLimit: This expression initializes the loopVariable to the lowerLimit and sends the message. Then the loopVariable is incremented and tested against the upperLimit. As long as the loopVariable is in range, the message is sent again. These upper and lower limits may be integers or ordered enumerations of values. For example, the messages 4*[thisMonth=Jan..Dec] and 4*[thisMonthNumber=1..12] would both execute 12 times.
Codelike looping syntax: UML allows you to write the iteration clause using the target programming language. Although there is some value to this practice, I wouldn’t recommend tying your model to your programming language; after all, the language could change in the future. Also, your UML tool may not understand the syntax exactly—so it probably won’t generate high-quality code.
In Figure, I’ve used the loopVariable approach in two locations. Look at message 3.3 from the Bill to the Stay. This tells us how the returned out arguments (sd and ed) are set and where their results go. Upon return, the two arguments, that of sd and ed, are set to the startDate attribute of the Stay and the endDate attribute for the Stay. Then, these values are saved in the Bill as Bill.sd and Bill.ed. Later, in message 3.4*, the Bill uses the sd and ed as (respectively) the lower and upper limit for a loop. The Bill sets up a loop with a loopVariable of thisDay and asks the Stay to retrieve the total charges for this day, via the call 3.4*[thisDay=sd..ed]: getTotalCharges(thisDay).
Message 3.4.1 is sent inside this loop to [thisDay]:Lodging, which illustrates that the loopIndex value, thisDay, (being passed in as a parameter in 3.4*) is being used by the Stay to find (or select) the correct Lodging. Within 3.4.1, the Lodging asks the RoomRate object for information on the rate. Every time the 3.4 loop iterates, you have message 3.4.1 sent, and then message 22.214.171.124 is sent.
Messages 3.4.2 and 3.4.3 are also sent within the 3.4 loop. Message 3.4.2 is an operation call sent by the Stay to itself, to return the number of Charge objects associated with it for the current day getNumCharges(thisDay). The result returns as numCharge.
This result is then used to construct another loop—an inner loop that uses the index thisCharge and loops from 1 to the numCharges. As both loops—thisDay and thisCharge—are going on at the same time, you can use both loop indices to select the charge on which you want to operate.