UML 基础: 序列图

It's February, and by now you've probably read about, or heard people talk about, making the change to UML 2.0--the new specification for UML that contains a number of improvements. Given the importance of the new spec, we are changing the basis of this article series, too, shifting our attention from OMG's UML 1.4 Specification to OMG's Adopted 2.0 Draft Specification of UML (a.k.a. UML 2). I hate to change emphasis from 1.4 to 2.0 in the middle of a series of articles, but the UML 2.0 Draft Specification is an important step forward, and I feel the need to spread the word.

There were a couple of reasons that the OMG improved UML. The main reason was that they wanted UML models to be capable of delivering Model Driven Architecture (MDA), which meant that the UML had to function as a more model driven notation. Also, the UML 1.x notation set was at times difficult to apply to larger applications. Furthermore, the notation elements needed to be improved in order to make diagrams more readable. (For example, modeling logical flow in UML 1.x was complicated and at times impossible. Changes to the sequence diagram's notation set in UML 2 have made vast improvements in modeling logic in sequences.)

Notice the wording in my statement above: "Adopted 2.0 Draft Specification of UML." It is true that the specification is still in draft status, but the key is that the Draft Specification has been adopted by OMG, a consortium that does not adopt new standards until they become pretty solid. There will be some changes to the specification before UML 2 is completely adopted, but these changes should be minimal. The main changes will be in the internals of UML--involving features typically used by software companies who implement UML tools.

The main purpose of this article is to continue our focus on the essential UML diagrams; this month, we take a close look at the sequence diagram. Please note, again, that the examples provided below are based on the new UML 2 specification.

The diagram's purpose

The sequence diagram is used primarily to show the interactions between objects in the sequential order that those interactions occur. Much like the class diagram, developers typically think sequence diagrams were meant exclusively for them. However, an organization's business staff can find sequence diagrams useful to communicate how the business currently works by showing how various business objects interact. Besides documenting an organization's current affairs, a business-level sequence diagram can be used as a requirements document to communicate requirements for a future system implementation. During the requirements phase of a project, analysts can take use cases to the next level by providing a more formal level of refinement. When that occurs, use cases are often refined into one or more sequence diagrams.

An organization's technical staff can find sequence diagrams useful in documenting how a future system should behave. During the design phase, architects and developers can use the diagram to force out the system's object interactions, thus fleshing out overall system design.

One of the primary uses of sequence diagrams is in the transition from requirements expressed as use cases to the next and more formal level of refinement. Use cases are often refined into one or more sequence diagrams. In addition to their use in designing new systems, sequence diagrams can be used to document how objects in an existing (call it "legacy") system currently interact. This documentation is very useful when transitioning a system to another person or organization.


The notation

Since this is the first article in my UML diagram series that is based on UML 2, we need to first discuss an addition to the notation in UML 2 diagrams, namely a notation element called a frame. The frame element is used as a basis for many other diagram elements in UML 2, but the first place most people will encounter a frame element is as the graphical boundary of a diagram. A frame element provides a consistent place for a diagram's label, while providing a graphical boundary for the diagram. The frame element is optional in UML diagrams; as you can see in Figures 1 and 2, the diagram's label is placed in the top left corner in what I'll call the frame's "namebox," a sort of dog-eared rectangle, and the actual UML diagram is defined within the body of the larger enclosing rectangle.

Figure 1: An empty UML 2 frame element

In addition to providing a visual border, the frame element also has an important functional use in diagrams depicting interactions, such as the sequence diagram. On sequence diagrams incoming and outgoing messages (a.k.a. interactions) for a sequence can be modeled by connecting the messages to the border of the frame element (as seen in Figure 2). This will be covered in more detail in the "Beyond the basics" section below.

Figure 2: A sequence diagram that has incoming and outgoing messages

Notice that in Figure 2 the diagram's label begins with the letters "sd," for Sequence Diagram. When using a frame element to enclose a diagram, the diagram's label needs to follow the format of:

Diagram Type Diagram Name

The UML specification provides specific text values for diagram types (e.g., sd = Sequence Diagram, activity = Activity Diagram, and use case = Use Case Diagram).


The basics

The main purpose of a sequence diagram is to define event sequences that result in some desired outcome. The focus is less on messages themselves and more on the order in which messages occur; nevertheless, most sequence diagrams will communicate what messages are sent between a system's objects as well as the order in which they occur. The diagram conveys this information along the horizontal and vertical dimensions: the vertical dimension shows, top down, the time sequence of messages/calls as they occur, and the horizontal dimension shows, left to right, the object instances that the messages are sent to.

Lifelines

When drawing a sequence diagram, lifeline notation elements are placed across the top of the diagram. Lifelines represent either roles or object instances that participate in the sequence being modeled. 1 Lifelines are drawn as a box with a dashed line descending from the center of the bottom edge (Figure 3). The lifeline's name is placed inside the box.

Figure 3: An example of the Student class used in a lifeline whose instance name is freshman

The UML standard for naming a lifeline follows the format of:

Instance Name : Class Name

In the example shown in Figure 3, the lifeline represents an instance of the class Student, whose instance name is freshman. Note that, here, the lifeline name is underlined. When an underline is used, it means that the lifeline represents a specific instance of a class in a sequence diagram, and not a particular kind of instance (i.e., a role). In a future article we'll look at structure modeling. For now, just observe that sequence diagrams may include roles (such as buyer and seller) without specifying who plays those roles (such as Bill and Fred). This allows diagram reuse in different contexts. Simply put, instance names in sequence diagrams are underlined; roles names are not.

Our example lifeline in Figure 3 is a named object, but not all lifelines represent named objects. Instead a lifeline can be used to represent an anonymous or unnamed instance. When modeling an unnamed instance on a sequence diagram, the lifeline's name follows the same pattern as a named instance; but instead of providing an instance name, that portion of the lifeline's name is left blank. Again referring to Figure 3, if the lifeline is representing an anonymous instance of the Student class, the lifeline would be: " Student." Also, because sequence diagrams are used during the design phase of projects, it is completely legitimate to have an object whose type is unspecified: for example, "freshman."

Messages

The first message of a sequence diagram always starts at the top and is typically located on the left side of the diagram for readability. Subsequent messages are then added to the diagram slightly lower then the previous message.

To show an object (i.e., lifeline) sending a message to another object, you draw a line to the receiving object with a solid arrowhead (if a synchronous call operation) or with a stick arrowhead (if an asynchronous signal). The message/method name is placed above the arrowed line. The message that is being sent to the receiving object represents an operation/method that the receiving object's class implements. In the example in Figure 4, the analyst object makes a call to the system object which is an instance of the ReportingSystem class. The analyst object is calling the system object's getAvailableReports method. The system object then calls the getSecurityClearance method with the argument of userId on the secSystem object, which is of the class type SecuritySystem. 2

Figure 4: An example of messages being sent between objects

Besides just showing message calls on the sequence diagram, the Figure 4 diagram includes return messages. These return messages are optional; a return message is drawn as a dotted line with an open arrowhead back to the originating lifeline, and above this dotted line you place the return value from the operation. In Figure 4 the secSystem object returns userClearance to the system object when the getSecurityClearance method is called. The system object returns availableReports when the getAvailableReports method is called.

Again, the return messages are an optional part of a sequence diagram. The use of return messages depends on the level of detail/abstraction that is being modeled. Return messages are useful if finer detail is required; otherwise, the invocation message is sufficient. I personally like to include return messages whenever a value will be returned, because I find the extra details make a sequence diagram easier to read.

When modeling a sequence diagram, there will be times that an object will need to send a message to itself. When does an object call itself? A purist would argue that an object should never send a message to itself. However, modeling an object sending a message to itself can be useful in some cases. For example, Figure 5 is an improved version of Figure 4. The Figure 5 version shows the system object calling its determineAvailableReports method. By showing the system sending itself the message "determineAvailableReports," the model draws attention to the fact that this processing takes place in the system object.

To draw an object calling itself, you draw a message as you would normally, but instead of connecting it to another object, you connect the message back to the object itself.

Figure 5: The system object calling its determineAvailableReports method

The example messages in Figure 5 show synchronous messages; however, in sequence diagrams you can model asynchronous messages, too. An asynchronous message is drawn similar to a synchronous one, but the message's line is drawn with a stick arrowhead, as shown in Figure 6.

Figure 6: A sequence diagram fragment showing an asynchronous message being sent to instance2

Guards

When modeling object interactions, there will be times when a condition must be met for a message to be sent to the object. Guards are used throughout UML diagrams to control flow. Here, I will discuss guards in both UML 1.x as well as UML 2.0. In UML 1.x, a guard could only be assigned to a single message. To draw a guard on a sequence diagram in UML 1.x, you placed the guard element above the message line being guarded and in front of the message name. Figure 7 shows a fragment of a sequence diagram with a guard on the message addStudent method.

Figure 7: A segment of a UML 1.x sequence diagram in which the addStudent message has a guard

In Figure 7, the guard is the text "[pastDueBalance = 0]." By having the guard on this message, the addStudent message will only be sent if the accounts receivable system returns a past due balance of zero. The notation of a guard is very simple; the format is:

[Boolean Test]

For example,

[pastDueBalance = 0]

Combined fragments (alternatives, options, and loops)

In most sequence diagrams, however, the UML 1.x "in-line" guard is not sufficient to handle the logic required for a sequence being modeled. This lack of functionality was a problem in UML 1.x. UML 2 has addressed this problem by removing the "in-line" guard and adding a notation element called a Combined Fragment. A combined fragment is used to group sets of messages together to show conditional flow in a sequence diagram. The UML 2 specification identifies 11 interaction types for combined fragments. Three of the eleven will be covered here in "The Basics" section, two more types will be covered in the "Beyond The Basics" section, and the remaining six I will leave to be covered in another article. (Hey, this is an article, not a book. I want you to finish this piece in one day!)

Alternatives

Alternatives are used to designate a mutually exclusive choice between two or more message sequences. 3 Alternatives allow the modeling of the classic "if then else" logic (e.g., if I buy three items, then I get 20% off my purchase; else I get 10% off my purchase).

As you will notice in Figure 8, an alternative combination fragment element is drawn using a frame. The word "alt" is placed inside the frame's namebox. The larger rectangle is then divided into what UML 2 calls operands. 4 Operands are separated by a dashed line. Each operand is given a guard to test against, and this guard is placed towards the top left section of the operand on top of a lifeline. 5 If an operand's guard equates to "true," then that operand is the operand to follow.

Figure 8: A sequence diagram fragment that contains an alternative combination fragment

As an example to show how an alternative combination fragment is read, Figure 8 shows the sequence starting at the top, with the bank object getting the check's amount and the account's balance. At this point in the sequence the alternative combination fragment takes over. Because of the guard "[balance >= amount]," if the account's balance is greater than or equal to the amount, then the sequence continues with the bank object sending the addDebitTransaction and storePhotoOfCheck messages to the account object. However, if the balance is not greater than or equal to the amount, then the sequence proceeds with the bank object sending the addInsuffientFundFee and noteReturnedCheck message to the account object and the returnCheck message to itself. The second sequence is called when the balance is not greater than or equal to the amount because of the "[else]" guard. In alternative combination fragments, the "[else]" guard is not required; and if an operand does not have an explicit guard on it, then the "[else]" guard is to be assumed.

Alternative combination fragments are not limited to simple "if then else" tests. There can be as many alternative paths as are needed. If more alternatives are needed, all you must do is add an operand to the rectangle with that sequence's guard and messages.

Option

The option combination fragment is used to model a sequence that, given a certain condition, will occur; otherwise, the sequence does not occur. An option is used to model a simple "if then" statement (i.e., if there are fewer than five donuts on the shelf, then make two dozen more donuts).

The option combination fragment notation is similar to the alternation combination fragment, except that it only has one operand and there never can be an "else" guard (it just does not make sense here). To draw an option combination you draw a frame. The text "opt" is placed inside the frame's namebox, and in the frame's content area the option's guard is placed towards the top left corner on top of a lifeline. Then the option's sequence of messages is placed in the remainder of the frame's content area. These elements are illustrated in Figure 9.

Figure 9: A sequence diagram fragment that includes an option combination fragment

Reading an option combination fragment is easy. Figure 9 is a reworking of the sequence diagram fragment in Figure 7, but this time it uses an option combination fragment because more messages need to be sent if the student's past due balance is equal to zero. According to the sequence diagram in Figure 9, if a student's past due balance equals zero, then the addStudent, getCostOfClass, and chargeForClass messages are sent. If the student's past due balance does not equal zero, then the sequence skips sending any of the messages in the option combination fragment.

The example Figure 9 sequence diagram fragment includes a guard for the option; however, the guard is not a required element. In high-level, abstract sequence diagrams you might not want to specify the condition of the option. You may simply want to indicate that the fragment is optional.

Loops

Occasionally you will need to model a repetitive sequence. In UML 2, modeling a repeating sequence has been improved with the addition of the loop combination fragment.

The loop combination fragment is very similar in appearance to the option combination fragment. You draw a frame, and in the frame's namebox the text "loop" is placed. Inside the frame's content area the loop's guard 6 is placed towards the top left corner, on top of a lifeline. Then the loop's sequence of messages is placed in the remainder of the frame's content area. In a loop, a guard can have two special conditions tested against in addition to the standard Boolean test. The special guard conditions are minimum iterations written as "minint = [the number]" (e.g., "minint = 1") and maximum iterations written as "maxint = [the number]" (e.g., "maxint = 5"). With a minimum iterations guard, the loop must execute at least the number of times indicated, whereas with a maximum iterations guard the number of loop executions cannot exceed the number.

Figure 10: An example sequence diagram with a loop combination fragment
click to enlarge

The loop shown in Figure 10 executes until the reportsEnu object's hasAnotherReport message returns false. The loop in this sequence diagram uses a Boolean test to verify if the loop sequence should be run. To read this diagram, you start at the top, as normal. When you get to the loop combination fragment a test is done to see if the value hasAnotherReport equals true. If the hasAnotherReport value equals true, then the sequence goes into the loop fragment. You can then follow the messages in the loop as you would normally in a sequence diagram


Beyond the basics

I've covered the basics of the sequence diagram, which should allow you to model most of the interactions that will take place in a common system. The following section will cover more advanced notation elements that can be used in a sequence diagram.

Referencing another sequence diagram

When doing sequence diagrams, developers love to reuse existing sequence diagrams in their diagram's sequences. 7 Starting in UML 2, the "Interaction Occurrence" element was introduced. The addition of interaction occurrences is arguably the most important innovation in UML 2 interactions modeling. Interaction occurrences add the ability to compose primitive sequence diagrams into complex sequence diagrams. With these you can combine (reuse) the simpler sequences to produce more complex sequences. This means that you can abstract out a complete, and possibly complex, sequence as a single conceptual unit.

An interaction occurrence element is drawn using a frame. The text "ref" is placed inside the frame's namebox, and the name of the sequence diagram being referenced is placed inside the frame's content area along with any parameters to the sequence diagram. The notation of the referenced sequence diagram's name follows the pattern of:

sequence diagram name[(arguments)] [: return value]

Two examples:

1. Retrieve Borrower Credit Report(ssn) : borrowerCreditReport

or

2. Process Credit Card(name, number, expirationDate, amount : 100)

In example 1, the syntax calls the sequence diagram called Retrieve Borrower Credit Report and passes it the parameter ssn. The Retreive Borrower Credit Report sequence returns the variable borrowerCreditReport.

In example 2, the syntax calls the sequence diagram called Process Credit Card and passes it the parameters of name, number, expiration date, and amount. However, in example 2 the amount parameter will be a value of 100. And since example 2 does not have a return value labeled, the sequence does not return a value (presumably, the sequence being modeled does not need the return value).

Figure 11: A sequence diagram that references two different sequence diagrams

Figure 11 shows a sequence diagram that references the sequence diagrams "Balance Lookup" and "Debit Account." The sequence starts at the top left, with the customer sending a message to the teller object. The teller object sends a message to the theirBank object. At that point, the Balance Lookup sequence diagram is called, with the accountNumber passed as a parameter. The Balance Lookup sequence diagram returns the balance variable. Then the option combination fragment's guard condition is checked to verify the balance is greater then the amount variable. In cases where the balance is greater than the amount, the Debit Account sequence diagram is called, passing it the accountNumber and the amount as parameters. After that sequence is complete, the withdrawCash message returns cash to the customer.

It is important to notice in Figure 11 that the lifeline of theirBank is hidden by the interaction occurrence Balance Lookup. Because the interaction occurrence hides the lifeline, that means that the theirBank lifeline is referenced in the "Balance Lookup" sequence diagram. In addition to hiding the lifeline in the interaction occurrence, UML 2 also specifies that the lifeline must have the same theirBank in its own "Balance Lookup" sequence.

There will be times when you model sequence diagrams that an interaction occurrence will overlap lifelines that are not referenced in the interaction occurrence. In such cases the lifeline is shown as a normal lifeline and is not hidden by the overlapping interaction occurrence.

In Figure 11, the sequence references the "Balance Lookup" sequence diagram. The "Balance Lookup" sequence diagram is shown in Figure 12. Because the example sequence has parameters and a return value, its label --located in the diagram's namebox--follows a specific pattern:

Diagram Type Diagram Name [(Parameter Type : Parameter Name)] : 
      
[: Return Value Type]

Two examples:

1. SD Balance Lookup(Integer : accountNumber) : Real

or

2. SD Available Reports(Financial Analyst : analyst) : Reports

Figure 12 illustrates example 1, in which the Balance Lookup sequence uses parameter accountNumber as a variable in the sequence, and the sequence diagram shows a Real object being returned. In cases such as this, where the sequence returns an object, the object being returned is given the instance name of the sequence diagram.

Figure 12: A sequence diagram that takes the parameter of accountNumber and returns a Real object

Figure 13 illustrates example 2, in which a sequence takes a parameter and returns an object. However, in Figure 13 the parameter is used in the sequence's interaction.

Figure 13: A sequence diagram that uses its parameter in its interaction and returns a Reports object
Click to enlarge

Gates

The previous section showed how to reference another sequence diagram by passing information through parameters and return values. However, there is another way to pass information between sequence diagrams. Gates can be an easy way to model the passing of information between a sequence diagram and its context. A gate is merely a message that is illustrated with one end connected to the sequence diagram's frame's edge and the other end connected to a lifeline. A reworking of Figures 11 and 12 using gates can be seen in Figures 14 and 15. The example diagram in Figure 15 has an entry gate called getBalance that takes the parameter of accountNumber. The getBalance message is an entry gate, because it is the arrowed line that is connected to the diagram's frame with the arrowhead connected to a lifeline. The sequence diagram also has an exit gate that returns the balance variable. The exit gate is known, because it's a return message that is connected from a lifeline to the diagram's frame with the arrowhead connected to the frame.

Figure 14: A reworking of Figure 11, using gates this time

Figure 15: A reworking of Figure 12, using gates this time

Combined fragments (break and parallel)

In the "basics" section presented earlier in this paper, I covered the combined fragments known as "alternative," "option," and "loop." These three combined fragments are the ones most people will use the most. However, there are two other combined fragments that a large share of people will find useful – break and parallel.

Break

The break combined fragment is almost identical in every way to the option combined fragment, with two exceptions. First, a break's frame has a namebox with the text "break" instead of "option." Second, when a break combined fragment's message is to be executed, the enclosing interaction's remainder messages will not be executed because the sequence breaks out of the enclosing interaction. In this way the break combined fragment is much like the break keyword in a programming language like C++ or Java.

Figure 16: A reworking of the sequence diagram fragment from Figure 8, with the fragment using a break instead of an alternative

Breaks are most commonly used to model exception handling. Figure 16 is a reworking of Figure 8, but this time Figure 16 uses a break combination fragment because it treats the balance < amount condition as an exception instead of as an alternative flow. To read Figure 16, you start at the top left corner of the sequence and read down. When the sequence gets to the return value "balance," it checks to see if the balance is less than the amount. If the balance is not less than the amount, the next message sent is the addDebitTransaction message, and the sequence continues as normal. However, in cases where the balance is less than the amount, then the sequence enters the break combination fragment and its messages are sent. Once all the messages in the break combination have been sent, the sequence exits without sending any of the remaining messages (e.g., addDebitTransaction).

An important thing to note about breaks is that they only cause the exiting of an enclosing interaction's sequence and not necessarily the complete sequence depicted in the diagram. In cases where a break combination is part of an alternative or a loop, then only the alternative or loop is exited.

Parallel

Today's modern computer systems are advancing in complexity and at times perform concurrent tasks. When the processing time required to complete portions of a complex task is longer than desired, some systems handle parts of the processing in parallel. The parallel combination fragment element needs to be used when creating a sequence diagram that shows parallel processing activities.

The parallel combination fragment is drawn using a frame, and you place the text "par" in the frame's namebox. You then break up the frame's content section into horizontal operands separated by a dashed line. Each operand in the frame represents a thread of execution done in parallel.

Figure 17: A microwave is an example of an object that does two tasks in parallel

While Figure 17 may not illustrate the best computer system example of an object doing activities in parallel, it offers an easy-to-understand example of a sequence with parallel activities. The sequence goes like this: A hungryPerson sends the cookFood message to the oven object. When the oven object receives that message, it sends two messages to itself at the same time (nukeFood and rotateFood). After both of these messages are done, the hungryPerson object is returned yummyFood from the oven object.


Conclusion

The sequence diagram is a good diagram to use to document a system's requirements and to flush out a system's design. The reason the sequence diagram is so useful is because it shows the interaction logic between the objects in the system in the time order that the interactions take place.


References


Notes

1 In fully modeled systems the objects (instances of classes) will also be modeled on a system's class diagram.

2 When reading this sequence diagram, assume that the analyst has already logged into the system.

3 Please note that it is indeed possible for two or more guard conditions attached to different alternative operands to be true at the same time, but at most only one operand will actually occur at run time (which alternative "wins" in such cases is not defined by the UML standard).

4 Although operands look a lot like lanes on a highway, I specifically did not call them lanes. Swim lanes are a UML notation used on activity diagrams. Please refer to The Rational Edge's earlier article about Activity Diagrams.

5 Usually, the lifeline to which the guard is attached is the lifeline that owns the variable that is included in the guard expression.

6 As with the option combination fragment, the loop combination fragment does not require that a guard condition be placed on it.

7 It's possible to reuse a sequence diagram of any type (e.g. programming or business). I just find that developers like to functionally break down their diagrams more.

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

序列图是一个用来记录系统需求,和整理系统设计的好图。序列图是如此好用的理由是,因为它按照交互发生的时间顺序,显示了系统中对象间的交互逻辑。

序列图的主要用途之一,是把用例表达的需求,转化为进一步、更加正式层次的精细表达。用例常常被细化为一个或者更多的序列图。序列图除了在设计新系统方面的用途外,它们还能用来记录一个存在系统(称它为“遗产”)的对象现在如何交互。当把这个系统移交给另一个人或组织时,这个文档很有用。

符号

既然这是我基于 UML 2的 UML 图系列文章的第一篇,我们需要首先讨论对 UML 2 图符号的一个补充,即一个叫做框架的符号元件。在 UML 2中,框架元件用于作为许多其他的图元件的一个基础,但是大多数人第一次接触框架元件的情况,是作为图的图形化边界。当为图提供图形化边界时,一个框架元件为图的标签提供一致的位置。在 UML 图中框架元件是可选择的;就如你能在图 1 和 2 中见到的,图的标签被放在左上角,在我将调用框架的“namebox”中,一种卷角长方形,而且实际的 UML 图在较大的封闭长方形内部定义。

图 1: 空的 UML 2 框架元件

图 1: 空的 UML 2 框架元件

除了提供一个图形化边框之外,用于图中的框架元件也有描述交互的重要的功能, 例如序列图。在序列图上一个序列接收和发送消息(又称交互),能通过连接消息和框架元件边界,建立模型(如图 2 所见到)。这将会在后面“超越基础”的段落中被更详细地介绍。

图 2: 一个接收和发送消息的序列图

图 2: 一个接收和发送消息的序列图

注意在图 2 中,对于序列图,图的标签由文字“sd”开始。当使用一个框架元件封闭一个图时,图的标签需要按照以下的格式:

图类型 图名称

UML 规范给图类型提供特定的文本值。(举例来说,sd代表序列图,activity代表活动图,use case代表用例图)。

基础

序列图的主要目的是定义事件序列,产生一些希望的输出。重点不是消息本身,而是消息产生的顺序;不过,大多数序列图会表示一个系统的对象之间传递的什么消息,以及它们发生的顺序。图按照水平和垂直的维度传递信息:垂直维度从上而下表示消息/调用发生的时间序列,而且水平维度从左到右表示消息发送到的对象实例。

生命线

当画一个序列图的时候,放置生命线符号元件,横跨图的顶部。生命线表示序列中,建模的角色或对象实例。 1 生命线画作一个方格,一条虚线从上而下,通过底部边界的中心(图 3)。生命线名字放置在方格里。

图 3: 用于一个实体名为freshman的生命线的Student类的一个例子

图 3: 用于一个实体名为freshman的生命线的Student类的一个例子

UML 的生命线命名标准按照如下格式:

实体名 : 类名

在如图3所示的例子中,生命线表示类Student的实体,它的实体名称是freshman。这里注意一点,生命线名称带下划线。当使用下划线时,意味着序列图中的生命线代表一个类的特定实体,不是特定种类的实体(例如,角色)。在将来的一篇文章中,我们将会了解结构化建模。现在,仅仅评述序列图,可能包含角色(例如买方卖方),而不需要叙述谁扮演那些角色(例如BillFred)。这准许不同语境的图重复使用。简单拖放,序列图的实例名称有下划线,而角色名称没有。

图 3 中我们生命线例子是一个命名的对象,但是不是所有的生命线都代表命名的对象。相反的,一个生命线能用来表现一个匿名的或未命名的实体。当在一个序列图上,为一个未命名的实例建模时,生命线的名字采用和一个命名实例相同的模式;但是生命线名字的位置留下空白,而不是提供一个例图名字。再次参考图 3,如果生命线正在表现Student类的一个匿名例图,生命线会是: “Student”。同时, 因为序列图在项目设计阶段中使用,有一个未指定的对象是完全合法: 举例来说,“freshman”。

消息

为了可读性,序列图的第一个消息总是从顶端开始,并且一般位于图的左边。然后继发的消息加入图中,稍微比前面的消息低些。

为了显示一个对象(例如,生命线)传递一个消息给另外一个对象,你画一条线指向接收对象,包括一个实心箭头(如果是一个同步调用操作)或一个棍形箭头(如果是一个异步讯号)。消息/方法名字放置在带箭头的线上面。正在被传递给接收对象的消息,表示接收对象的类实现的一个操作/方法。在图 4 的例子中,analyst对象调用ReportingSystem 类的一个实例的系统对象。analyst对象在调用系统对象的 getAvailableReports 方法。系统对象然后调用secSystem 对象上的、包括参数userId的getSecurityClearance 方法,secSystem的类的类型是 SecuritySystem。 2

图 4: 一个在对象之间传递消息的实例

图 4: 一个在对象之间传递消息的实例

除了仅仅显示序列图上的消息调用外,图 4 中的图还包括返回消息。这些返回消息是可选择的;一个返回消息画作一个带开放箭头的虚线,向后指向来源的生命线,在这条虚线上面,你放置操作的返回值。在图 4 中,当 getSecurityClearance 方法被调用时,secSystem 对象返回 userClearance 给系统对象。当 getAvailableReports 方法被调用时,系统对象返回 availableReports。

此外,返回消息是序列图的一个可选择部分。返回消息的使用依赖建模的具体/抽象程度。如果需要较好的具体化,返回消息是有用的;否则,主动消息就足够了。我个人喜欢,无论什么时候返回一个值,都包括一个返回消息,因为我发现额外的细节使一个序列图变得更容易阅读。

当序列图建模时,有时候,一个对象将会需要传递一个消息给它本身。一个对象何时称它本身?一个纯化论者会争辩一个对象应该永不传递一个消息给它本身。然而,为传递一个消息给它本身的对象建模,在一些情境中可能是有用的。举例来说,图 5 是图 4 的一个改良版本。 图 5 版本显示调用它的 determineAvailableReports 方法的系统对象。通过表示系统传递消息“determineAvailableReports”给它本身,模型把注意力集中到过程的事实上,而不是系统对象。

为了要画一个调用本身的对象,如你平时所作的,画一条消息,但是不是连接它到另外的一个对象,而是你把消息连接回对象本身。

图 5: 系统对象调用它的 determineAvailableReports 方法

图 5: 系统对象调用它的 determineAvailableReports 方法

图 5 中的消息实例显示同步消息;然而,在序列图中,你也能为异步消息建模。一个异步消息和一个同步的画法类似,但是消息画的线带一个棍形矛头,如图 6 所示。

图 6: 表示传递到实体2的异步消息的序列图片段

图 6: 表示传递到实体2的异步消息的序列图片段

约束

当为对象的交互建模时,有时候,必须满足一个条件,消息才会传递给对象。约束在 UML 图各处中,用于控制流。在这里,我将会讨论UML 1.x 及UML 2.0两者的约束。在 UML 1.x 中,一个约束只可能被分配到一个单一消息。UML 1.x中,为了在一个序列图上画一个约束,你把约束元件放在约束的消息线上,消息名字之前。图 7 显示序列图的一个片段,消息addStudent 方法上有一个约束。

图 7:UML 1.x 序列图的一个片段,其中addStudent 消息有一个约束

图 7:UML 1.x 序列图的一个片段,其中addStudent 消息有一个约束

在图 7 中,约束是文本“[ pastDueBalance=0]”。通过这个消息上的约束,如果应收帐系统返回一个零点的逾期平衡,addStudent 消息才将会被传递。约束的符号很简单;格式是:

[Boolean Test]

举例来说,

[pastDueBalance = 0]

组合碎片(变体方案,选择项,和循环)

然而,在大多数的序列图中,UML 1.x“in-line”约束不足以处理一个建模序列的必需逻辑。这个功能缺失是 UML 1.x 的一个问题。UML 2 已经通过去掉“in-line”约束,增加一个叫做组合碎片的符号元件,解决了这一个问题。一个组合碎片用来把一套消息组合在一起,在一个序列图中显示条件分支。UML 2 规范指明了组合碎片的 11 种交互类型。十一种中的三种将会在“基础”段落中介绍,另外两种类型将会在“超越基础”中介绍,而那剩余的六种我将会留在另一篇文章中介绍。(嗨,这是一篇文章而不是一本书。我希望你在一天中看完这部分!)

变体

变体用来指明在两个或更多的消息序列之间的、互斥的选择。 3 变体支持经典的“if then else”逻辑的建模(举例来说,如果 我买三个,然后 我得到 我购买的20% 折扣;否则 我得到我购买的 10% 折扣)。

就如你将会在图 8 中注意到的,一个变体的组合碎片元件使用框架来画。单词“alt”放置在框架的namebox里。然后较大的长方形分为 UML 2 所称的操作元。 4 操作元被虚线分开。每个操作元有一个约束进行测试,而这个约束被放置在生命线顶端的操作元的左上部。 5 如果操作元的约束等于“true”,然后那个操作元是要执行的操作元。

图 8:包含变体组合碎片的一个序列图片段

图 8:包含变体组合碎片的一个序列图片段

图 8作为一个变体的组合碎片如何阅读的例子,显示序列从顶部开始,即bank对象获取支票金额和帐户结余。此时,序列图中的变体组合碎片接管。因为约束“[balance >= amount]”,如果余额超过或等于金额,然后顺序进行bank对象传递 addDebitTransaction 和 storePhotoOfCheck 消息给account对象。然而,如果余额不是超过或等于金额,然后顺序的过程就是bank传递addInsuffientFundFee 和 noteReturnedCheck 消息给account对象,returnCheck 消息给它自身。因为“else”约束,当余额不大于或者等于金额时,第二个序列被调用。在变体的组合碎片中,不需要“else”约束;而如果一个操作元,在它上面没有一个明确的约束,那么将假定“else”约束。

变体的组合碎片没被限制在简单的“if then else”验证。可能需要大量的变体路径。 如果需要较多的变体方案,你一定要做的全部工作就是把一个操作元加入有序列约束和消息的长方形中。

选择项

选择项组合碎片用来为序列建模,这些序列给予一个特定条件,将会发生的;或者,序列不发生。一个选择项用来为简单的“if then”表达式建模。(例如,如果架上的圈饼少于五个,那么另外做两打圈饼)。

选择项组合碎片符号与变体组合碎片类似,除了它只有一个操作元并且永不能有“else”约束以外(它就是如此,没有理由)。要画选择项组合,你画一个框架。文字“opt”是被放置在框架的 namebox 里的文本,在框架的内容区,选择项的约束被放置在生命线顶端上的左上角。 然后选择项的消息序列被放在框架的内容区的其余位置内。这些元件如图 9 所示。

图 9:包括选择项组合碎片的一个序列图片段

图 9:包括选择项组合碎片的一个序列图片段

阅读选择项组合碎片很容易。图 9 是图 7 的序列图片段的再加工,但是这次它使用一个选择项组合碎片,因为如果Student的逾期平衡等于0,需要传递更多的消息。按照图 9 的序列图,如果Student的逾期平衡等于零,然后传递addStudent,getCostOfClass和chargeForClass消息。如果Student的逾期平衡不等于零,那么在选择项组合碎片中,序列不传递任何一个消息。

例子图 9的序列图片段包括一个选择项约束;然而,约束不是一个必需的元件。在高层次、抽象的序列图中,你可能不想叙述选择项的条件。你可能只是想要指出片段是可选择的。

循环

有时候你将会需要为一个重复的序列建模。在 UML 2 中,为一个重复的序列建模已经改良,附加了循环组合碎片。

循环组合碎片表面非常类似选择项组合碎片。你画一个框架,在框架的 namebox 中放置文本“loop”。在框架的内容区中,一个生命线的顶部,循环约束 6 被放置在左上角。然后循环的消息序列被放在框架内容区的其余部分中。在一个循环中,除了标准的布尔测试外,一个约束能测试二个特定的条件式。特定的约束条件式是写作“minint = [the number]”(例如,“minint = 1”)的最小循环次数,和写作“maxint = [the number]”(例如,“maxint = 5”)的最大循环次数。通过最小循环检验,循环必须运行至少指定次数,而循环执行次数不能达到约束指定的最大循环次数。

图 10:循环组合碎片的一个序列图例子

图 10:循环组合碎片的一个序列图例子

点击放大

在图 10 中显示的循环运行,直到 reportsEnu 对象的 hasAnotherReport 消息返回false。如果循环序列应该运行,这个序列图的循环使用一个布尔测试确认。为了阅读这个图,你和平常一样,从顶部开始。当你到达循环组合碎片,做一个测试,看看值 hasAnotherReport 是否等于true。如果 hasAnotherReport 值等于true,于是序列进入循环片断。然后你能和正常情况一样,在序列图中跟踪循环的消息。

超越基础

我已经介绍了序列图的基础,应该使你可以为将会在系统中通常发生的大部份交互建模。下面段落将会介绍用于序列图的比较高阶的符号元件。

引用另外一个序列图

当做序列图的时候,开发者爱在他们的序列图中,重用存在的序列图。 7 在 UML 2 中开始,引进“交互进行”元件。追加交互进行的可以说是 UML 2 交互建模中的最重要的创新。交互进行增加了功能,把原始的序列图组织成为复杂的序列图。由于这些,你能组合(重用)较简单的序列,生成比较复杂的序列。这意味你能把完整的、可能比较复杂的序列,抽象为一个单一的概念单位。

一个交互进行元件使用一个框架绘制。文字“ref”放置在框架的 namebox 中,引用的序列图名字放置在框架的内容区里,连同序列图的任何参数一起。引用序列图的名字符号如下模式:

序列图名[(参数)] [: 返回值]

两个例子:

1. Retrieve Borrower Credit Report(ssn) : borrowerCreditReport

或者

2. Process Credit Card(name, number, expirationDate, amount : 100)

在例子 1 中,语法调用叫做Retrieve Borrower Credit Report的序列图,传递给它参数 ssn。序列Retreive Borrower Credit Report返回变量 borrowerCreditReport 。

在实例 2 中,语法调用叫做Process Credit Card的序列图,传递给它参数name,number,expiration date,和 amount。然而,在例子 2 中,amount参数将会是值100。因为例子2没有返回值标签,序列不返回值(假设,建模的序列不需要返回值)。

图 11: 一个引用两个不同序列图的序列图

图 11: 一个引用两个不同序列图的序列图

图 11 显示一个序列图,它引用了序列图“Balance Lookup”和“Debit Account”。序列从左上角开始,客户传递一个消息给teller对象。teller对象传递一个消息给 theirBank 对象。那时,调用Balance Lookup序列图,而 accountNumber作为一个参数传递。Balance Lookup序列图返回balance变量。然后检验选择项组合碎片的约束条件,确认余额大于金额变量。在余额比金额更大的情况下,调用Debit Account序列图,给它传递参数accountNumber 和amount。在那个序列完成后,withdrawCash 消息为客户返回cash。

重要的是,注意在图 11 中,theirBank 的生命线被交互进行Balance Lookup隐藏了。因为交互进行隐藏生命线,意味着theirBank 生命线在“Balance Lookup”序列图中被引用。除了隐藏交互进行的生命线之外,UML 2 也指明,生命线在它自己的“Balance Lookup”序列中,一定有相同的 theirBank 。

有时候,你为一个序列图建模,其中交互进行会重叠没有 在交互进行中引用的生命线。在那种情况下,生命线和正常的生命线一样显示,不会被重叠的交互进行隐藏。

在图 11 中,序列引用“Balance Lookup”序列图。“Balance Lookup”序列图在图 12 中显示。因为例子序列有参数和一个返回值,它的标签 —— 位于图的 namebox 中 —— 按照一个特定模式:

图类型 图名 [参数类型:参数名]
[: 返回值类型]

两个例子:

1. SD Balance Lookup(Integer : accountNumber) : Real

2. SD Available Reports(Financial Analyst : analyst) : Reports

图 12 举例说明例子 1,在里面,Balance Lookup序列把参数 accountNumber 作为序列中的变量使用,序列图显示返回的Real对象。在类似这种情况下,返回的对象采用序列图实体名。

图 12: 一个使用 accountNumber 参数并返回一个Real对象的序列图

图 12: 一个使用 accountNumber 参数并返回一个Real对象的序列图

图 13 举例说明例子 2,在里面,一个序列图获取一个参数,返回一个对象。然而,在图 13 中参数在序列的交互中使用。

图 13: 一个在它的交互中使用参数、返回一个Reports对象的序列图

图 13: 一个在它的交互中使用参数、返回一个Reports对象的序列图

点击放大

前面的段落展示如何通过参数和返回值传递信息,引用另一个序列图。然而,有另一个方法在序列图之间传递消息。门可能是一个容易的方法,为在序列图和它的上下文之间的传递消息建模。一个门只是一个消息,图形表示为一端连接序列图的框架边缘,另一端连接到生命线。使用门的图 11 和 12 ,在图 14 和 15 中可以被看到重构。图 15 的例图有一个叫做getBalance的入口门,获取参数 accountNumber。因为是箭头的线连接到图的框架,而箭头连接到生命线,所以 getBalance 消息是一个入口门。序列图也有一个出囗门,返回balance变量。出口门同理可知,因为它是一个返回消息,连接从一个生命线到图的框架,箭头连接框架。

图 14: 图 11 的重构,这次使用门

图 14: 图 11 的重构,这次使用门

图 15: 图 12 的重构,这次使用门

图 15: 图 12 的重构,这次使用门

组合碎片(跳转和并行)

在本文前面“基础”的段落中呈现的,我介绍了“变体”,“选择项”,和“循环”的组合碎片。这些三个组合碎片是大多数人将会使用最多的。然而,有二个其他的组合碎片,大量共享的人将会发现有用——跳转和并行。

跳转

跳转组合碎片几乎在每个方面都和选择项组合碎片一致,除了两个例外。首先,跳转的框架namebox的文本“break”代替了“option”。其次, 当一个跳转组合碎片的消息运行时,封闭的交互作用的其他消息将不会执行,因为序列打破了封闭的交互。这样,跳转组合碎片非常象 C++ 或 Java 的编程语言中的break关键字。

图 16: 来自图 8 的序列图片段的重构,片段使用跳转代替变体

图 16: 来自图 8 的序列图片段的重构,片段使用跳转代替变体

跳转最常用来做模型异常处理。图 16 是图 8 的重构,但是这次图16使用跳转组合碎片,因为它把balance < amount的情况作为一个异常对待,而不是一个变体流。要阅读图 16,你从序列的左上角开始,向下读。当序列到达返回值“balance”的时候,它检查看看是否余额比金额更少。如果余额不少于金额,被传递的下一个消息是 addDebitTransaction 消息,而且序列正常继续。然而,在余额比金额更少的情况下,然后序列进入跳转组合碎片,它的消息被传递。一旦跳转组合的消息的已经被传递,序列不发送任何其它消息就退出(举例来说,addDebitTransaction)。

注意有关跳转的一件重要的事是,它们只引起一个封闭交互的序列退出,不必完成图中描述的序列。在这种情况下,跳转组合是变体或者循环的一部分,然后只是变体或循环被退出。

并行

今天的现代计算机系统在复杂性和有时执行并发任务方面不断进步。当完成一个复杂任务需要的处理时间比希望的长的时候,一些系统采用并行处理进程的各部分。当创造一个序列图,显示并行处理活动的时候,需要使用并行组合碎片元件。

并行组合碎片使用一个框架来画,你把文本“par”放在框架的 namebox 中。然后你把框架的内容段用虚线分为水平操作元。框架的每个操作元表示一个在并行运行的线程。

图 17: oven 是并行做两个任务的对象实例

图 17: oven 是并行做两个任务的对象实例

图 17 可能没有举例说明做并行活动的对象的最好的计算机系统实例,不过提供了一个容易理解的并行活动序列的例子。序列如这样进行:hungryPerson 传递 cookFood 消息给oven 对象。当oven 对象接收那个消息时,它同时发送两个消息(nukeFood 和 rotateFood)给它本身。这些消息都处理后,hungryPerson 对象从oven 对象返回 yummyFood 。

猜你喜欢

转载自xueqi.iteye.com/blog/1993987
今日推荐