UML/Sequence Diagrams
Sequence Diagrams
Soul-Learner
2007. 12. 4. 16:33
Sequence Diagrams
Sequence diagrams are the most common of the dynamic models drawn by UML slingers.
As you might expect, UML provides lots and lots of goodies to help you draw truly
incomprehensible diagrams. In this chapter we’ll study those goodies, and try to convince
you to use them with great restraint.
I once consulted for a team that had decided to create sequence diagram for every
method of evey class. No, no, no, no, no! Don’t do this, it’s a terrible waste of time. Use
sequence diagrams when you have an immediate need to describe to someone how a
group of objects collaborate, or when you want to visualize that collaboration for yourself.
Use them as a tool that you occasionally use to hone your analytical skills, rather than as
necessary documentation.
The Basics
I first learned to draw sequence diagrams in 1978. James Grenning, a long time friend and
associate, showed them to me while we were working on a project that involved complex
communication protocols between computers connected by modems. What I am going to
show you here is just a little more complex than what he taught me then; and should suffice
for the vast majority of sequence diagrams that you will need to draw.
Objects, Lifelines, Messages, and other odds and ends.
Figure 4-1 shows a typical sequence diagram. The objects involved in the collaboration
are shown at the top. The stick figure (actor) at left represents an anonymous object. It is
the source and sink of all the messages entering and leaving the collaboration. Not all
sequence diagrams have such an anonymous actor, but many do.
Figure 4-1 shows a typical sequence diagram. The objects involved in the collaboration
are shown at the top. The stick figure (actor) at left represents an anonymous object. It is
the source and sink of all the messages entering and leaving the collaboration. Not all
sequence diagrams have such an anonymous actor, but many do.
The dashed lines hanging down from the objects and the actor are called lifelines. A
message being sent from one object to another is shown as an arrow between the two lifelines.
Each message is labeled with its name. Arguments appear either in the parenthesis
that follow the name, or next to data tokens (the little arrows with the circles on the end).
Time is in the vertical dimension, so the lower a message appears the later it is sent.
The skinny little rectangle on the lifeline of the LoginServlet object is called an
activation. Activations are optional; most diagrams don’t need them. They represent the
time that a function executes. In this case it shows how long the login function runs. The
two messages leaving the activation to the right were sent by the login method. The unlabeled
arrow shows the login function returning to the actor, and passing back a return
value.
Note the use of the e variable in the getEmployee message. This signifies the value
returned by getEmployee. Notice also that the Employee object is named e. You guessed
it, they’re one and the same. The value that getEmployee returns is a reference to the
Employee object.
Finally, notice that EmployeeDB is a class, not an object. This can only mean that
getEmployee is a static method. Thus, we’d expect EmployeeDB to be coded as in Listing 4-1.
Creation and Destruction
We can show the creation of an object on a sequence diagram using the convention shown
in Figure 4-2. An unlabeled message terminates on the object to be created, not on it’s lifeline.
We would expect ShapFactory to be implemented as shown in Listing 4-2.
In Java we don’t explicitly destroy objects. The garbage collector does all the explicit
destruction for us. However, there are times when we want to make it clear that we are
done with an object and that, as far as we are concerned, the garbage collector can have it.
Figure 4-3 shows how we denote this in UML. The lifeline of the object to be released
comes to a premature end at a large X. The message arrow terminating on the X represents
the act of releasing the object to the garbage collector.
Listing 4-3 shows the implementation we might expect from this diagram. Notice that
the clear method sets the topNode variable to nil. Since the TreeMap is the only object
that holds a reference to that TreeNode instance, it will be released to the garbage collector.
Simple Loops
You can draw a simple loop in a UML diagram by drawing a box around the messages that repeat. The loop condition can appear somewhere in the box, usually at the lower left. See Figure 4-4.
This is a useful notational convention. However, it is not wise to try to capture algorithms
in sequence diagrams. Sequence diagrams should be used to expose the connections
between objects not the nitty gritty details of an algorithm.
Cases and Scenarios
Rule: Don’t draw sequence diagrams like Figure 4-5 with lots of objects and scores of
messages. Nobody can read them. Nobody will read them. They’re a huge waste of time.
Rather, learn how to draw a few smaller sequence diagrams that capture the essense of
what you are trying to do. Each sequence diagram should fit on a single page, with plenty
of room left for explanatory text. You should not have to shrink the icons down to tiny
sizes to get them to fit on the page.
Also, don’t draw dozens or hundreds of sequence diagrams. If you have too many,
they won’t be read. Find out what’s common about all the scenarios and focus on that. In
the world of UML diagrams, commonalities are much more important than differences.
Use your diagrams to show common themes and common practices. Don’t use them to
document every little detail. If you really need to draw a sequence diagram to describe the way messages flow, then do them succinctly, and sparingly. Draw as few of them as possible.
First of all, ask youself if the sequence diagram is necessary at all. Code is often more
communicative and economical. Listing 4-4, for example, shows what the code for the
Payroll class might look like. This code is very expressive, and stands on its own. We
don’t need the sequence diagram to understand it. So perhaps there’s no need to draw the
sequence diagram. Perhaps the code is good enough to stand on its own. When code can
stand on its own, then diagrams are redundant and wasteful.
Can code really be used to describe part of a system? In fact, this should be a goal of
the developers and designers. The team should strive to create code that is expressive and
readable. The more the code can describe itself, the fewer diagrams you will need, and the
better of the whole project will be.
Secondly, if you feel a sequence diagram is necessary, ask yourself if there is a way to
split it up into a small group of scenarios. For example, we could break the large sequence
diagram in Figure 4-5 into several much smaller sequence diagrams that would be much
easier to read. Consider how much easier the small scenario in Figure 4-6 is to understand.
Thirdly, think about what you are trying to depict. Are you trying to show the details
of a low level operation like Figure 4-6 shows how to calculate hourly pay? Or are you
trying to show a high level view of the overall flow of the system as in Figure 4-7? In general,
high level diagrams are more useful than low level ones. They help the reader tie the
system together in his mind. They expose commonalities more than differences.
Advanced Concepts
Loops and Conditions
It is possible to draw a sequence diagram that completely specifies an algorithm. In Figure
4-8 you can see the payroll algorithm, complete with well specified loops and if statements.
The payEmployee message is prefixed with an recurrence expression that looks like
this:
*[while id := idList.next()]
The star tells us that this is an iteration; the message will be sent repeatedly until the
guard expression in the square brackets is false. UML does not specify a syntax for the
guard expression so I have used a java-like pseudo code that suggests the use of an iterator.
The payEmployee message terminates on an activation that is touching, but offset
from, the first. This denotes that there are now two functions executing in the same object.
Since the payEmployee message is recurrent, the second activation will also be recurrent,
and so all the messages depending from it will be part of the loop.
Note the activation that is near the [payday] guard. This denotes an if statement.
The second activation only gets control if the guard condition is true. Thus, if isPayDay
returns true, then calculatePay, calculateDeductions, and sendPayment will be
executed. Otherwise they won’t.
The fact that it is possible to capture all the details of an algorithm in a sequence diagram
should not be construed as a license to capture all your algorithms in this manner.
The depiction of algorithms in UML is clunky at best. The code in Listing 4-4 is a much
better way of expressing the algorithm.
Asynchronous Messages.
Usually, when you send a message to an object you don’t expect to get control back until
the recieving object has finished executing. Messagse that behave this way are called synchronous
messages. However, in distributed or multi-threaded systems it is possible for
the sending object to get control back immediately, and for the recieving object to execute
in another thread of control. Such messages are called asynchronous messages.
Figure 4-11shows an asynchronous message. Note that the arrowhead is open instead
of filled. Look back at all the other sequence diagrams in this chapter. They were all
drawn with synchronous (filled arrowhead) messages. It is the elegance (or perversity,
take your pick) of UML that such a subtle difference in the arrowhead can have such a
profound difference in the represented behavior.
Multiple Threads
Asynchronous messages imply multiple threads of control. We can show several different
threads of control in a UML diagram by tagging the message name with a thread identifier
as shown in Figure 4-12.
Notice that the name of the message is prefixed with an identifier such as T1, followed
by a colon. This identifier names the thread that the messages was sent from. In the
diagram, the Log object was created and manipulated by thread T1. The thread that actually
does the message logging, running inside the Log object, is named T2.
As you can see, the thread identifiers don’t necessarily correspond to names in the
code. Listing 4-6 above does not name the logging thread T2. Rather, the thread identifiers
are for the benefit of the diagram.
Active Objects
Sometimes we want to denote that an object has a seperate internal thread. Such objects
are known as active objects. They are shown with a bold outline as in Figure 4-13.
Active objects simply objects that instantiate and control their own thread. There are
no restrictions about their methods. Their methods may run in the object’s thread, or they
may run in the caller’s thread.
Sending Messages to Interfaces.
Our Log class is just one way to log messages. What if we wanted our application to be
able to use many different kinds of loggers. We’d probably create a Logger interface that
declared the logMessage method, and derive our Log class, and all the other implementations
from that interface. See Figure 4-14.
The application is going to be sending messages to the Logger interface. It won’t
know that the object is an AsychronousLogger. How can we depict this in a sequence
diagram?
The diagram in Figure 4-15 is the obvious approach. You just name the object for the
interface and be done with it. This may seem to break the rules since it’s impossible to
have an instance of an interface. However, all we are saying here is that the logger object conforms to the Logger type. We aren’t saying that we somehow managed to instantiate
an interface.
Sometimes, however, know the type of the object and yet want to show the message
being sent to an interface. For example, we might know that we have created an AsynchronousLogger,
but we still want to show the application using only the Logger interface.
Figure 4-16 shows how this is depicted. We use the interface lollipop on the lifeline
of the object.
Sequence diagrams are the most common of the dynamic models drawn by UML slingers.
As you might expect, UML provides lots and lots of goodies to help you draw truly
incomprehensible diagrams. In this chapter we’ll study those goodies, and try to convince
you to use them with great restraint.
I once consulted for a team that had decided to create sequence diagram for every
method of evey class. No, no, no, no, no! Don’t do this, it’s a terrible waste of time. Use
sequence diagrams when you have an immediate need to describe to someone how a
group of objects collaborate, or when you want to visualize that collaboration for yourself.
Use them as a tool that you occasionally use to hone your analytical skills, rather than as
necessary documentation.
The Basics
I first learned to draw sequence diagrams in 1978. James Grenning, a long time friend and
associate, showed them to me while we were working on a project that involved complex
communication protocols between computers connected by modems. What I am going to
show you here is just a little more complex than what he taught me then; and should suffice
for the vast majority of sequence diagrams that you will need to draw.
Objects, Lifelines, Messages, and other odds and ends.
Figure 4-1 shows a typical sequence diagram. The objects involved in the collaboration
are shown at the top. The stick figure (actor) at left represents an anonymous object. It is
the source and sink of all the messages entering and leaving the collaboration. Not all
sequence diagrams have such an anonymous actor, but many do.
Figure 4-1 shows a typical sequence diagram. The objects involved in the collaboration
are shown at the top. The stick figure (actor) at left represents an anonymous object. It is
the source and sink of all the messages entering and leaving the collaboration. Not all
sequence diagrams have such an anonymous actor, but many do.
message being sent from one object to another is shown as an arrow between the two lifelines.
Each message is labeled with its name. Arguments appear either in the parenthesis
that follow the name, or next to data tokens (the little arrows with the circles on the end).
Time is in the vertical dimension, so the lower a message appears the later it is sent.
The skinny little rectangle on the lifeline of the LoginServlet object is called an
activation. Activations are optional; most diagrams don’t need them. They represent the
time that a function executes. In this case it shows how long the login function runs. The
two messages leaving the activation to the right were sent by the login method. The unlabeled
arrow shows the login function returning to the actor, and passing back a return
value.
Note the use of the e variable in the getEmployee message. This signifies the value
returned by getEmployee. Notice also that the Employee object is named e. You guessed
it, they’re one and the same. The value that getEmployee returns is a reference to the
Employee object.
Finally, notice that EmployeeDB is a class, not an object. This can only mean that
getEmployee is a static method. Thus, we’d expect EmployeeDB to be coded as in Listing 4-1.
We can show the creation of an object on a sequence diagram using the convention shown
in Figure 4-2. An unlabeled message terminates on the object to be created, not on it’s lifeline.
We would expect ShapFactory to be implemented as shown in Listing 4-2.
destruction for us. However, there are times when we want to make it clear that we are
done with an object and that, as far as we are concerned, the garbage collector can have it.
Figure 4-3 shows how we denote this in UML. The lifeline of the object to be released
comes to a premature end at a large X. The message arrow terminating on the X represents
the act of releasing the object to the garbage collector.
Listing 4-3 shows the implementation we might expect from this diagram. Notice that
the clear method sets the topNode variable to nil. Since the TreeMap is the only object
that holds a reference to that TreeNode instance, it will be released to the garbage collector.
You can draw a simple loop in a UML diagram by drawing a box around the messages that repeat. The loop condition can appear somewhere in the box, usually at the lower left. See Figure 4-4.
in sequence diagrams. Sequence diagrams should be used to expose the connections
between objects not the nitty gritty details of an algorithm.
Cases and Scenarios
Rule: Don’t draw sequence diagrams like Figure 4-5 with lots of objects and scores of
messages. Nobody can read them. Nobody will read them. They’re a huge waste of time.
Rather, learn how to draw a few smaller sequence diagrams that capture the essense of
what you are trying to do. Each sequence diagram should fit on a single page, with plenty
of room left for explanatory text. You should not have to shrink the icons down to tiny
sizes to get them to fit on the page.
they won’t be read. Find out what’s common about all the scenarios and focus on that. In
the world of UML diagrams, commonalities are much more important than differences.
Use your diagrams to show common themes and common practices. Don’t use them to
document every little detail. If you really need to draw a sequence diagram to describe the way messages flow, then do them succinctly, and sparingly. Draw as few of them as possible.
First of all, ask youself if the sequence diagram is necessary at all. Code is often more
communicative and economical. Listing 4-4, for example, shows what the code for the
Payroll class might look like. This code is very expressive, and stands on its own. We
don’t need the sequence diagram to understand it. So perhaps there’s no need to draw the
sequence diagram. Perhaps the code is good enough to stand on its own. When code can
stand on its own, then diagrams are redundant and wasteful.
Can code really be used to describe part of a system? In fact, this should be a goal of
the developers and designers. The team should strive to create code that is expressive and
readable. The more the code can describe itself, the fewer diagrams you will need, and the
better of the whole project will be.
Secondly, if you feel a sequence diagram is necessary, ask yourself if there is a way to
split it up into a small group of scenarios. For example, we could break the large sequence
diagram in Figure 4-5 into several much smaller sequence diagrams that would be much
easier to read. Consider how much easier the small scenario in Figure 4-6 is to understand.
Thirdly, think about what you are trying to depict. Are you trying to show the details
of a low level operation like Figure 4-6 shows how to calculate hourly pay? Or are you
trying to show a high level view of the overall flow of the system as in Figure 4-7? In general,
high level diagrams are more useful than low level ones. They help the reader tie the
system together in his mind. They expose commonalities more than differences.
Loops and Conditions
It is possible to draw a sequence diagram that completely specifies an algorithm. In Figure
4-8 you can see the payroll algorithm, complete with well specified loops and if statements.
this:
*[while id := idList.next()]
The star tells us that this is an iteration; the message will be sent repeatedly until the
guard expression in the square brackets is false. UML does not specify a syntax for the
guard expression so I have used a java-like pseudo code that suggests the use of an iterator.
The payEmployee message terminates on an activation that is touching, but offset
from, the first. This denotes that there are now two functions executing in the same object.
Since the payEmployee message is recurrent, the second activation will also be recurrent,
and so all the messages depending from it will be part of the loop.
Note the activation that is near the [payday] guard. This denotes an if statement.
The second activation only gets control if the guard condition is true. Thus, if isPayDay
returns true, then calculatePay, calculateDeductions, and sendPayment will be
executed. Otherwise they won’t.
The fact that it is possible to capture all the details of an algorithm in a sequence diagram
should not be construed as a license to capture all your algorithms in this manner.
The depiction of algorithms in UML is clunky at best. The code in Listing 4-4 is a much
better way of expressing the algorithm.
Asynchronous Messages.
Usually, when you send a message to an object you don’t expect to get control back until
the recieving object has finished executing. Messagse that behave this way are called synchronous
messages. However, in distributed or multi-threaded systems it is possible for
the sending object to get control back immediately, and for the recieving object to execute
in another thread of control. Such messages are called asynchronous messages.
Figure 4-11shows an asynchronous message. Note that the arrowhead is open instead
of filled. Look back at all the other sequence diagrams in this chapter. They were all
drawn with synchronous (filled arrowhead) messages. It is the elegance (or perversity,
take your pick) of UML that such a subtle difference in the arrowhead can have such a
profound difference in the represented behavior.
Multiple Threads
Asynchronous messages imply multiple threads of control. We can show several different
threads of control in a UML diagram by tagging the message name with a thread identifier
as shown in Figure 4-12.
by a colon. This identifier names the thread that the messages was sent from. In the
diagram, the Log object was created and manipulated by thread T1. The thread that actually
does the message logging, running inside the Log object, is named T2.
As you can see, the thread identifiers don’t necessarily correspond to names in the
code. Listing 4-6 above does not name the logging thread T2. Rather, the thread identifiers
are for the benefit of the diagram.
Active Objects
Sometimes we want to denote that an object has a seperate internal thread. Such objects
are known as active objects. They are shown with a bold outline as in Figure 4-13.
no restrictions about their methods. Their methods may run in the object’s thread, or they
may run in the caller’s thread.
Sending Messages to Interfaces.
Our Log class is just one way to log messages. What if we wanted our application to be
able to use many different kinds of loggers. We’d probably create a Logger interface that
declared the logMessage method, and derive our Log class, and all the other implementations
from that interface. See Figure 4-14.
The application is going to be sending messages to the Logger interface. It won’t
know that the object is an AsychronousLogger. How can we depict this in a sequence
diagram?
The diagram in Figure 4-15 is the obvious approach. You just name the object for the
interface and be done with it. This may seem to break the rules since it’s impossible to
have an instance of an interface. However, all we are saying here is that the logger object conforms to the Logger type. We aren’t saying that we somehow managed to instantiate
an interface.
Sometimes, however, know the type of the object and yet want to show the message
being sent to an interface. For example, we might know that we have created an AsynchronousLogger,
but we still want to show the application using only the Logger interface.
Figure 4-16 shows how this is depicted. We use the interface lollipop on the lifeline
of the object.



