Modeling - CL2 Flashcards
UML: Structure Diagrams
One of the 2 main types of UML diagrams. Defines a set of subtypes: 1. Class Class, features, and relationships 2. Component Structure and connections of components 3. Composite structure Runtime decomposition of a class 4. Deployment Deployment of artifacts to nodes 5. Object Example configurations of instances 6. Package Compile-time hierarchic structure
UML: Behavior Diagrams
One of the 2 main types of UML diagrams. Defines a set of subtypes:
1. Activity
Procedural and parallel behavior
2. State machine
How events change an object over its life
3. Use case
How users interact with a system
4. Interaction diagrams subset (Sequence, Communication, Interaction Overview, Timing)
UML: Interaction Diagrams
A subset of behavior diagrams: 1. Sequence Interaction between objects; emphasis on sequence 2. Communication Interaction between objects; emphasis on links 3. Interaction overview Mix of sequence and activity diagram 4. Timing Interaction between objects; emphasis on timing
Class Diagram (Advanced)
The concepts described in Chapter 3 correspond to the key notations in class diagrams. Those concepts are the first ones to understand and become familiar with, as they will comprise 90 percent of your effort in building class diagrams. The class diagram technique, however, has bred dozens of notations for additional concepts. I find that I don't use these all the time, but they are handy when they are appropriate. I'll discuss them one at a time and point out some of the issues in using them. You'll probably find this chapter somewhat heavy going. The good news is that during your first pass through the book, you can safely skip this chapter and come back to it later.
Keywords
One of the challenges of a graphical language is that you have to remember what the symbols
mean. With too many, users find it very difficult to remember what all the symbols mean. So the
UML often tries to reduce the number of symbols and use keywords instead. If you find that you
need a modeling construct that isn’t in the UML but is similar to something that is, use the symbol of
the existing UML construct but mark it with a keyword to show that you have something different
An example of this is the interface. A UML interface (page 69) is a class that has only public
operations, with no method bodies. This corresponds to interfaces in Java, COM (Component Object
Module), and CORBA. Because it’s a special kind of class, it is shown using the class icon with the
keyword «interface». Keywords are usually shown as text between guillemets. As an alternative to
keywords, you can use special icons, but then you run into the issue of everyone having to
remember what they mean.
Some keywords, such as {abstract}, show up in curly brackets. It’s never really clear what should
technically be in guillemets and what should be in curlies. Fortunately, if you get it wrong, only
serious UML weenies will notice—or care.
Some keywords are so common that they often get abbreviated: «interface» often gets
abbreviated to «I» and {abstract} to {A}. Such abbreviations are very useful, particularly on
whiteboards, but nonstandard, so if you use them, make sure you find a spot to spell out what they
mean.
In UML 1, the guillemets were used mainly for stereotypes. In UML 2, stereotypes are defined very
tightly, and describing what is and isn’t a stereotype is beyond the scope of this book. However, because of UML 1, many people use the term stereotype to mean the same as keyword, although
that is no longer correct.
Stereotypes are used as part of profiles. A profile takes a part of the UML and extends it with a
coherent group of stereotypes for a particular purpose, such as business modeling. The full
semantics of profiles are beyond this book. Unless you are into serious meta-model design, you’re
unlikely to need to create one yourself. You’re more likely to use one created for a specific modeling
purpose, but fortunately, use of a profile doesn’t require you to know the gory details of how they
are tied into the meta-model.
Classification and Generalization
I often hear people talk about subtyping as the is a relationship. I urge you to beware of that way of
thinking. The problem is that the phrase is a can mean different things.
Consider the following phrases.
1. Shep is a Border Collie.
2. A Border Collie is a Dog.
3. Dogs are Animals.
4. A Border Collie is a Breed.
5. Dog is a Species.
Now try combining the phrases. If I combine phrases 1 and 2, I get “Shep is a Dog”; 2 and 3 taken
together yield “Border Collies are Animals.” And 1 plus 2 plus 3 gives me “Shep is an Animal.” So
far, so good. Now try 1 and 4: “Shep is a Breed.” The combination of 2 and 5 is “A Border Collie is a
Species.” These are not so good.
Why can I combine some of these phrases and not others? The reason is that some are
classification—the object Shep is an instance of the type Border Collie—and some are
generalization—the type Border Collie is a subtype of the type Dog. Generalization is transitive;
classification is not. I can combine a classification followed by a generalization but not vice versa.
I make this point to get you to be wary of is a. Using it can lead to inappropriate use of subclassing
and confused responsibilities. Better tests for subtyping in this case would be the phrases “Dogs are
kinds of Animals” and “Every instance of a Border Collie is an instance of a Dog.”
The UML uses the generalization symbol to show generalization. If you need to show classification,
use a dependency with the «instantiate» keyword.
Multiple and Dynamic Classification Classification refers to the relationship between an object and its type. Mainstream programming languages assume that an object belongs to a single class. But there are more options to classification than that. In single classification, an object belongs to a single type, which may inherit from supertypes. In multiple classification, an object may be described by several types that are not necessarily connected by inheritance. Multiple classification is different from multiple inheritance. Multiple inheritance says that a type may have many supertypes but that a single type must be defined for each object. Multiple classification allows multiple types for an object without defining a specific type for the purpose. For example, consider a person subtyped as either man or woman, doctor or nurse, patient or not (see Figure 5.11). Multiple classification allows an object to have any of these types assigned to it in any allowable combination, without the need for types to be defined for all the legal combinations. If you use multiple classification, you need to be sure that you make it clear which combinations are legal. UML 2 does this by placing each generalization relationship into a generalization set. On the class diagram, you label the generalization arrowhead with the name of the generalization set, which in UML 1 was called the discriminator. Single classification corresponds to a single generalization set with no name. Generalization sets are by default disjoint: Any instance of the supertype may be an instance of only one of the subtypes within that set. If you roll up generalizations into a single arrow, they must all be part of the same generalization set, as shown in Figure 5.11. Alternatively, you can have several arrows with the same text label. To illustrate, note the following legal combinations of subtypes in the diagram: (Female, Patient, Nurse); (Male, Physiotherapist); (Female, Patient); and (Female, Doctor, Surgeon). The combination (Patient, Doctor, Nurse) is illegal because it contains two types from the role generalization set. Another question is whether an object may change its class. For example, when a bank account is overdrawn, it substantially changes its behavior. Specifically, several operations, including "withdraw" and "close," get overridden. Dynamic classification allows objects to change class within the subtyping structure; static classification does not. With static classification, a separation is made between types and states; dynamic classification combines these notions. Should you use multiple, dynamic classification? I believe that it is useful for conceptual modeling. For software perspectives, however, the distance between it and the implementations is too much of a leap. In the vast majority of UML diagrams, you'll see only single static classification, so that should be your default.
Association Class
Association classes allow you to add attributes, operations, and other features to associations, as
shown in Figure 5.12. We can see from the diagram that a person may attend many meetings. We need to keep information about how awake that person was; we can do this by adding the attribute attentiveness to the association.
Figure 5.13 shows another way to represent this information: Make Attendance a full class in its own
right. Note how the multiplicities have moved.
What benefit do you gain with the association class to offset the extra notation you have to
remember? The association class adds an extra constraint, in that there can be only one instance of
the association class between any two participating objects. I feel the need for another example.
Take a look at the two diagrams in Figure 5.14. These diagrams have much the same form.
However, we can imagine one Company playing different roles in the same Contract, but it’s harder
to imagine a Person having multiple competencies in the same skill; indeed, you would probably
consider that an error.
In the UML, only the latter case is legal. You can have only one competency for each combination of
Person and Skill. The top diagram in Figure 5.14 would not allow a Company to have more than one
Role on a single contract. If you need to allow this, you need to make Role a full class, in the style of
Figure 5.13.
Implementing association classes isn’t terribly obvious. My advice is to implement an association
class as if it where a full class but to provide methods that get information to the classes linked by
the association class. So for Figure 5.12, I would see the following methods on Person:
class Person
List getAttendances()
List getMeetings()
This way, a client of Person can get hold of the people at the meeting; if they want details, they can
get the Attendances themselves. If you do this, remember to enforce the constraint that there can
be only one Attendance object for any pair of Person and Meeting. You should place a check in
whichever method creates the Attendance.
You often find this kind of construct with historical information, such as in Figure 5.15. However, I
find that creating extra classes or association classes can make the model tricky to understand, as
well as tilt the implementation in a particular direction that’s often unsuitable.
Template (Parameterized) Class
Several languages, most noticeably C++, have the notion of a parameterized class, or template.
(Templates are on the list to be included in Java and C# in the near future.)
This concept is most obviously useful for working with collections in a strongly typed language. This
way, you can define behavior for sets in general by defining a template class Set.
class Set {
void insert (T newElement);
void remove (T anElement);
When you have done this, you can use the general definition to make Set classes for more specific
elements:
Set employeeSet;
You declare a template class in the UML by using the notation shown in Figure 5.17. The T in the
diagram is a placeholder for the type parameter. (You may have more than one.)
A use of a parameterized class, such as Set, is called a derivation. You can show a
derivation in two ways. The first way mirrors the C++ syntax (see Figure 5.18). You describe the
derivation expression within angle brackets in the form . If there’s only one parameter, conventional use often omits the parameter name. The alternative
notation (see Figure 5.19) reinforces the link to the template and allows you to rename the bound
element.
The «bind» keyword is a stereotype on the refinement relationship. This relationship indicates that
EmployeeSet will conform to the interface of Set. You can think of the EmployeeSet as a subtype of
Set. This fits the other way of implementing type-specific collections, which is to declare all
appropriate subtypes.
Using a derivation is not the same as subtyping, however. You are not allowed to add features to the
bound element, which is completely specified by its template; you are adding only restricting type
information. If you want to add features, you must create a subtype.
Enumerations Enumerations (Figure 5.20) are used to show a fixed set of values that don't have any properties other than their symbolic value. They are shown as the class with the «enumeration» keyword.
Active Class
An active class has instances, each of which executes and controls its own thread of control.
Method invocations may execute in a client’s thread or in the active object’s thread. A good example
of this is a command processor that accepts command objects from the outside and then executes
the commands within its own thread of control.
The notation for active classes has changed from UML 1 to UML 2, as shown in Figure 5.21. In UML
2, an active class has extra vertical lines on the side; in UML 1, it had a thick border and was called
an active object.
Visibility
Visibility is a subject that is simple in principle but has complex subtleties. The simple idea is that
any class has public and private elements. Public elements can be used by any other class; private
elements can be used only by the owning class. However, each language makes its own rules.
Although many languages use such terms as public, private, and protected, they mean different
things in different languages. These differences are small, but they lead to confusion, especially for
those of us who use more than one language.
The UML tries to address this without getting into a horrible tangle. Essentially, within the UML, you
can tag any attribute or operation with a visibility indicator. You can use any marker you like, and its
meaning is language dependent. However, the UML provides four abbreviations for visibility: +
(public), – (private), ~ (package), and # (protected). These four levels are used within the UML
meta-model and are defined within it, but their definitions vary subtly from those in other
languages.
When you are using visibility, use the rules of the language in which you are working. When you are
looking at a UML model from elsewhere, be wary of the meanings of the visibility markers, and be
aware of how those meanings can change from language to language.
Most of the time, I don’t draw visibility markers in diagrams; I use them only if I need to highlight
the differences in visibility of certain features. Even then, I can mostly get away with + and –, which
at least are easy to remember.
Messages
Standard UML does not show any information about message calls on class diagrams. However, I’ve
sometimes seen conventional diagrams like Figure 5.22.
These add arrows to the sides of associations. The arrows are labeled with the messages that one
object sends to another. Because you don’t need an association to a class to send a message to it,
you may also need to add a dependency arrow to show messages between classes that aren’t
associated.
This message information spans multiple use cases, so they aren’t numbered to show sequences,
unlike communication diagrams.
Responsibilities Often, it's handy to show responsibilities (page 63) on a class in a class diagram. The best way to show them is as comment strings in their own compartment in the class (Figure 5.1). You can name the compartment, if you wish, but I usually don't, as there's rarely any potential for confusion.
Static Operations and Attributes The UML refers to an operation or an attribute that applies to a class rather than to an instance as static. This is equivalent to static members in C-based languages. Static features are underlined on a class diagram (see Figure 5.2).
Aggregation and Composition
One of the most frequent sources of confusion in the UML is aggregation and composition. It’s easy
to explain glibly: Aggregation is the part-of relationship. It’s like saying that a car has an engine
and wheels as its parts. This sounds good, but the difficult thing is considering what the difference is
between aggregation and association.
In the pre-UML days, people were usually rather vague on what was aggregation and what was
association. Whether vague or not, they were always inconsistent with everyone else. As a result,
many modelers think that aggregation is important, although for different reasons. So the UML
included aggregation (Figure 5.3) but with hardly any semantics. As Jim Rumbaugh says, “Think of it
as a modeling placebo” [Rumbaugh, UML Reference].
As well as aggregation, the UML has the more defined property of composition. In Figure 5.4, an
instance of Point may be part of a polygon or may be the center of a circle, but it cannot be both.
The general rule is that, although a class may be a component of many other classes, any instance
must be a component of only one owner. The class diagram may show multiple classes of potential
owners, but any instance has only a single object as its owner.
You’ll note that I don’t show the reverse multiplicities in Figure 5.4. In most cases, as here, it’s 0..1.
Its only other possible value is 1, for cases in which the component class is designed so that it can
have only one other class as its owner.
The “no sharing” rule is the key to composition. Another assumption is that if you delete the
polygon, it should automatically ensure that any owned Points also are deleted.
Composition is a good way of showing properties that own by value, properties to value objects
(page 73), or properties that have a strong and somewhat exclusive ownership of particular other
components. Aggregation is strictly meaningless; as a result, I recommend that you ignore it in your
own diagrams. If you see it in other people’s diagrams, you’ll need to dig deeper to find out what
they mean by it. Different authors and teams use it for very different purposes.
Derived Properties
Derived properties can be calculated based on other values. When we think about a date range
(Figure 5.5), we can think of three properties: the start date, the end date, and the number of days
in the period. These values are linked, so we can think of the length as being derived from the other
two values.
Derivation in software perspectives can be interpreted in a couple of different ways. You can use
derivation to indicate the difference between a calculated value and a stored value. In this case, we
would interpret Figure 5.5 as indicating that the start and end are stored but that the length is
computed. Although this is a common use, I’m not so keen, because it reveals too much of the
internals of DateRange.
My preferred thinking is that it indicates a constraint between values. In this case, we are saying
that the constraint among the three values holds, but it isn’t important which of the three values is
computed. In this case, the choice of which attribute to mark as derived is arbitrary and strictly
unnecessary, but it’s useful to help remind people of the constraint. This usage also makes sense
with conceptual diagrams.
Derivation can also be applied to properties using association notation. In this case, you simply
mark the name with a /.
Interfaces and Abstract Classes
An abstract class is a class that cannot be directly instantiated. Instead, you instantiate an
instance of a subclass. Typically, an abstract class has one or more operations that are abstract. An
abstract operation has no implementation; it is pure declaration so that clients can bind to the
abstract class.
The most common way to indicate an abstract class or operation in the UML is to italicize the name.
You can also make properties abstract, indicating an abstract property or accessor methods. Italics
are tricky to do on a whiteboards, so you can use the label: {abstract}.
An interface is a class that has no implementation; that is, all its features are abstract. Interfaces
correspond directly to interfaces in C# and Java and are a common idiom in other typed languages.
You mark an interface with the keyword «interface».
Classes have two kinds of relationships with interfaces: providing and requiring. A class provides
an interface if it is substitutable for the interface. In Java and .NET, a class can do that by
implementing the interface or implementing a subtype of the interface. In C++, you subclass the
class that is the interface.
A class requires an interface if it needs an instance of that interface in order to work. Essentially,
this is having a dependency on the interface.
Figure 5.6 shows these relationships in action, based on a few collection classes from Java. I might
write an Order class that has a list of line items. Because I’m using a list, the Order class is
dependent on the List interface. Let’s assume that it uses the methods equals, add, and get.
When the objects connect, the Order will actually use an instance of ArrayList but need not know
that in order to use those three methods, as they are all part of the List interface.
The ArrayList itself is a subclass of the AbstractList class. AbstractList provides some, but not
all, the implementation of the List behavior. In particular, the get method is abstract. As a result,
ArrayList implements get but also overrides some of the other operations on AbstractList. In
this case, it overrides add but is happy to inherit the implementation of equals.
Why don’t I simply avoid this and have Order use ArrayList directly? By using the interface, I allow
myself the advantage of making it easier to change implementations later on if I need to. Another
implementation may provide performance improvements, some database interaction features, or
other benefits. By programming to the interface rather than to the implementation, I avoid having to
change all the code should I need a different implementation of List. You should always try to
program to an interface like this; always use the most general type you can.
I should also point out a pragmatic wrinkle in this. When programmers use a collection like this,
they usually initialize the collection with a declaration, like this:
private List lineItems = new ArrayList();
Note that this strictly introduces a dependency from Order to the concrete ArrayList. In theory,
this is a problem, but people don’t worry about it in practice. Because the type of lineItems is
declared as List, no other part of the Order class is dependent on ArrayList. Should we change
the implementation, there’s only this one line of initialization code that we need to worry about. It’s
quite common to refer to a concrete class once during creation but to use only the interface
afterward.
The full notation of Figure 5.6 is one way to notate interfaces. Figure 5.7 shows a more compact
notation. The fact that ArrayList implements List and Collection is shown by having ball icons,
often referred to as lollipops, out of it. The fact that Order requires a List interface is shown by the
socket icon. The connection is nicely obvious.
The UML has used the lollipop notation for a while, but the socket notation is new to UML 2. (I think
it’s my favorite notational addition.) You’ll probably see older diagrams use the style of Figure 5.8,
where a dependency stands in for the socket notation.
Any class is a mix of an interface and an implementation. Therefore, we may often see an object
used through the interface of one of its superclasses. Strictly, it wouldn’t be legal to use the lollipop
notation for a superclass, as the superclass is a class, not a pure interface. But I bend these rules
for clarity.
As well as on class diagrams, people have found lollipops useful elsewhere. One of the perennial
problems with interaction diagrams is that they don’t provide a very good visualization for
polymorphic behavior. Although it’s not normative usage, you can indicate this along the lines of
Figure 5.9. Here, we can see that, although we have an instance of Salesman, which is used as such
by the Bonus Calculator, the Pay Period object uses the Salesman only through its Employee
interface. (You can do the same trick with communication diagrams.)
Read-Only and Frozen On page 37, I described the {readOnly} keyword. You use this keyword to mark a property that can only be read by clients and that cannot be updated. Similar yet different is the {frozen} keyword from UML 1. A property is frozen if it cannot change during the lifetime of an object; such properties are often called immutable. Although it was dropped from UML 2, {frozen} is a very useful concept, so I would continue to use it. As well as marking individual properties as frozen, you can apply the keyword to a class to indicate that all properties of all instances are frozen. (I have heard that frozen may well be reinstated shortly.)
Reference Objects and Value Objects
One of the common things said about objects is that they have identity. This is true, but it is not
quite as simple as that. In practice, you find that identity is important for reference objects but not
so important for value objects.
Reference objects are such things as Customer. Here, identity is very important because you
usually want only one software object to designate a customer in the real world. Any object that
references a Customer object will do so through a reference, or pointer; all objects that reference
this Customer will reference the same software object. That way, changes to a Customer are
available to all users of the Customer.
If you have two references to a Customer and wish to see whether they are the same, you usually
compare their identities. Copies may be disallowed; if they are allowed, they tend to be made rarely,
perhaps for archive purposes or for replication across a network. If copies are made, you need to
sort out how to synchronize changes.
Value objects are such things as Date. You often have multiple value objects representing the
same object in the real world. For example, it is normal to have hundreds of objects that designate
1-Jan-04. These are all interchangeable copies. New dates are created and destroyed frequently.
If you have two dates and wish to see whether they are the same, you don’t look at their identities
but rather at the values they represent. This usually means that you have to write an equality test
operator, which for dates would make a test on year, month, and day—or whatever the internal
representation is. Each object that references 1-Jan-04 usually has its own dedicated object, but you
can also share dates.
Value objects should be immutable; in other words, you should not be able to take a date object of
1-Jan-04 and change the same date object to be 2-Jan-04. Instead, you should create a new 2-Jan-
04 object and use that instead. The reason is that if the date were shared, you would update
another object’s date in an unpredictable way, a problem referred to as aliasing.
In days gone by, the difference between reference objects and value objects was clearer. Value
objects were the built-in values of the type system. Now you can extend the type system with your
own classes, so this issue requires more thought.
The UML uses the concept of data type, which is shown as a keyword on the class symbol. Strictly,
data type isn’t the same as value object, as data types can’t have identity. Value objects may have
an identity, but don’t use it for equality. Primitives in Java would be data types, but dates would not,
although they would be value objects.
If it’s important to highlight them, I use composition when associating with a value object. You can
also use a keyword on a value type; common conventional ones I see are «value» or «struct».
Qualified Associations
A qualified association is the UML equivalent of a programming concept variously known as
associative arrays, maps, hashes, and dictionaries. Figure 5.10 shows a way that uses a qualifier to
represent the association between the Order and Order Line classes. The qualifier says that in
connection with an Order, there may be one Order Line for each instance of Product.
From a software perspective, this qualified association would imply an interface along the lines of
class Order …
public OrderLine getLineItem(Product aProduct);
public void addLineItem(Number amount, Product forProduct);
Thus, all access to a given Order Line requires a Product as an argument, suggesting an
implementation using a key and value data structure.
It’s common for people to get confused about the multiplicities of a qualified association. In Figure
5.10, an Order may have many Line Items, but the multiplicity of the qualified association is the
multiplicity in the context of the qualifier. So the diagram says that an Order has 0..1 Line Items per
Product. A multiplicity of 1 would indicate that Order would have to have a Line Item for every
instance of Product. A * would indicate that you would have multiple Line Items per Product but that
access to the Line Items is indexed by Product.
In conceptual modeling, I use the qualifier construct only to show constraints along the lines of
“single Order Line per Product on Order.”
Component Diagram
A debate that’s always ranged large in the OO community is what the difference is between a
component and any regular class. This is not a debate that I want to settle here, but I can show you
the notation the UML uses to distinguish between them.
UML 1 had a distinctive symbol for a component (Figure 14.1). UML 2 removed that icon but allows
you to annotate a class box with a similar-looking icon. Alternatively, you can use the «component»
keyword.
Other than the icon, components don’t introduce any notation that we haven’t already seen.
Components are connected through implemented and required interfaces, often using the ball-and-
socket notation (page 71) just as for class diagrams. You can also decompose components by using
composite structure diagrams.
Figure 14.2 shows an example component diagram. In this example, a sales till can connect to a
sales server component, using a sales message interface. Because the network is unreliable, a
message queue component is set up so the till can talk to the server when the network is up and
talk to a queue when the network is down; the queue will then talk to the server when the network
becomes available. As a result, the message queue both supplies the sales message interface to talk
with the till and requires that interface to talk with the server. The server is broken down into two
major components. The transaction processor realizes the sales message interface, and the
accounting driver talks to the accounting system.
As I’ve already said, the issue of what is a component is the subject of endless debate. One of the
more helpful statements I’ve found is this:
Components are not a technology. Technology people seem to find this hard to
understand. Components are about how customers want to relate to software. They
want to be able to buy their software a piece at a time, and to be able to upgrade it
just like they can upgrade their stereo. They want new pieces to work seamlessly with
their old pieces, and to be able to upgrade on their own schedule, not the
manufacturer’s schedule. They want to be able to mix and match pieces from various
manufacturers. This is a very reasonable requirement. It is just hard to satisfy .
Ralph Johnson, http://www.c2.com/cgi/wiki?DoComponentsExist
The important point is that components represent pieces that are independently purchasable and
upgradeable. As a result, dividing a system into components is as much a marketing decision as it is
a technical decision, for which [Hohmann] is an excellent guide. It’s also a reminder to beware of overly fine-grained components, because too many components are hard to manage, especially
when versioning rears its ugly head, hence “DLL hell.”
In earlier versions of the UML, components were used to represent physical structures, such as
DLLs. That’s no longer true; for this task, you now use artifacts (page 97).
When to Use Component Diagrams
Use component diagrams when you are dividing your system into components and want to show
their interrelationships through interfaces or the breakdown of components into a lower-level
structure.
Package Diagram
Package Diagrams Classes represent the basic form of structuring an object-oriented system. Although they are wonderfully useful, you need something more to structure large systems, which may have hundreds of classes. A package is a grouping construct that allows you to take any construct in the UML and group its elements together into higher-level units. Its most common use is to group classes, and that's the way I'm describing it here, but remember that you can use packages for every other bit of the UML as well. In a UML model, each class is a member of a single package. Packages can also be members of other packages, so you are left with a hierarchic structure in which top-level packages get broken down into subpackages with their own subpackages and so on until the hierarchy bottoms out in classes. A package can contain both subpackages and classes. In programming terms, packages correspond to such grouping constructs as packages (in Java) and namespaces (in C++ and .NET). Each package represents a namespace, which means that every class must have a unique name within its owning package. If I want to create a class called Date, and a Date class is already in the System package, I can have my Date class as long as I put it in a separate package. To make it clear which is which, I can use a fully qualified name, that is, a name that shows the owning package structure. You use double colons to show package names in UML, so the dates might be System::Date and MartinFowler::Util::Date. In diagrams, packages are shown with a tabbed folder, as in Figure 7.1. You can simply show the package name or show the contents too. At any point, you can use fully qualified names or simply regular names. Showing the contents with class icons allows you to show all the details of a class, even to the point of showing a class diagram within the package. Simply listing the names makes sense when all you want to do is indicate which classes are in which packages.
It’s quite common to see a class labeled something like Date (from java.util) rather than the
fully qualified form. This style is a convention that was done a lot by Rational Rose; it isn’t part of
the standard.
The UML allows classes in a package to be public or private. A public class is part of the interface of
the package and can be used by classes in other packages; a private class is hidden. Different
programming environments have different rules about visibility between their packaging constructs;
you should follow the convention of your programming environment, even if it means bending the
UML’s rules.
A useful technique here is to reduce the interface of the package by exporting only a small subset of
the operations associated with the package’s public classes. You can do this by giving all classes
private visibility, so that they can be seen only by other classes in the same package, and by adding
extra public classes for the public behavior. These extra classes, called Facades [Gang of Four], then
delegate public operations to their shyer companions in the package.
How do you choose which classes to put in which packages? This is actually quite an involved
question that needs a good bit of design skill to answer. Two useful principles are the Common
Closure Principle and Common Reuse Principle [Martin]. The Common Closure Principle says that the
classes in a package should need changing for similar reasons. The Common Reuse Principle says
that classes in a package should all be reused together. Many of the reasons for grouping classes in
packages have to do with the dependencies between the packages, which I’ll come to next.
Packages and Dependencies A package diagram shows packages and their dependencies. I introduced the concept of dependency on page 47. If you have packages for presentation and domain, you have a dependency from the presentation package to the domain package if any class in the presentation package has a dependency to any class in the domain package. In this way, interpackage dependencies summarize the dependencies between their contents. The UML has many varieties of dependency, each with particular semantics and stereotype. I find it easier to begin with the unstereotyped dependency and use the more particular dependencies only if I need to, which I hardly ever do. In a medium to large system, plotting a package diagram can be one of the most valuable things you can do to control the large-scale structure of the system. Ideally, this diagram should be generated from the code base itself, so that you can see what is really there in the system. A good package structure has a clear flow to the dependencies, a concept that's difficult to define but often easier to recognize. Figure 7.2 shows a sample package diagram for an enterprise application, one that is well-structured and has a clear flow. Often, you can identify a clear flow because all the dependencies run in a single direction. Although that is a good indicator of a well-structured system, the data mapper packages of Figure 7.2 show an exception to that rule of thumb. The data mapper packages act as an insulating layer between the domain and database packages, an example of the Mapper pattern [Fowler, P of EAA]. Many authors say that there should be no cycles in the dependencies (the Acyclic Dependency Principle [Martin]). I don't treat that as an absolute rule, but I do think that cycles should be localized and that, in particular, you shouldn't have cycles that cross layers. The more dependencies coming into a package, the more stable the package's interface needs to be, as any change in its interface will ripple into all the packages that are dependent on it (the Stable Dependencies Principle [Martin]). So in Figure 7.2, the asset domain package needs a more stable interface than the leasing data mapper package. Often, you'll find that the more stable packages tend to have a higher proportion of interfaces and abstract classes (the Stable Abstractions Principle [Martin]. The dependency relationships are not transitive (page 48). To see why this is important for dependencies, look at Figure 7.2 again. If a class in the asset domain package changes, we may have a change to classes within the leasing domain package. But this change does not necessarily ripple through to the leasing presentation. (It ripples only if the leasing domain changes its interface.) Some packages are used in so many places that it would be a mess to draw all the dependency lines to them. In this case, a convention is to use a keyword, such as «global», on the package. UML packages also define constructs to allow packages to import and merge classes from one package into another, using dependencies with keywords to notate this. However, rules for this kind of thing vary greatly with programming languages. On the whole, I find the general notion of dependencies to be far more useful in practice.
Package Aspects
If you think about Figure 7.2, you’ll realize that the diagram has two kinds of structures. One is a
structure of layers in the application: presentation, domain, data mapper, and database. The other
is a structure of subject areas: leasing and assets.
You can make this more apparent by separating the two aspects, as in Figure 7.3. With this
diagram, you can clearly see each aspect. However, these two aspects aren’t true packages,
because you can’t assign classes to a single package. (You would have to pick one from each
aspect.) This problem mirrors the problem in the hierarchic namespaces in programming languages.
Although diagrams like Figure 7.3 are nonstandard UML, they are often very helpful in explaining the
structure of a complex application.
Implementing Packages Often, you'll see a case in which one package defines an interface that can be implemented by a number of other packages, such as that of Figure 7.4. In this case, the realization relationship indicates that the database gateway defines an interface and that the other gateway classes provide an implementation. In practice, this would mean that the database gateway package contains interfaces and abstract classes that are fully implemented by the other packages. It's quite common for an interface and its implementation to be in separate packages. Indeed, a client package often contains an interface for another package to implement: the same notion of required interface that I discussed on page 70. Imagine that we want to provide some user interface (UI) controls to turn things on and off. We want this to work with a lot of different things, such as heaters and lights. The UI controls need to invoke methods on the heater, but we don't want the controls to have a dependency to the heater. We can avoid this dependency by defining in the controls package an interface that is then implemented by any class that wants to work with these controls, as in Figure 7.5. This is an example of the pattern Separated Interface [Fowler, P of EAA].
When to Use Package Diagrams
I find package diagrams extremely useful on larger-scale systems to get a picture of the
dependencies between major elements of a system. These diagrams correspond well to common
programming structures. Plotting diagrams of packages and dependencies helps you keep an
application’s dependencies under control.
Package diagrams represent a compile-time grouping mechanism. For showing how objects are
composed at runtime, use a composite structure diagram (page 135).
Deployment Diagram
Deployment diagrams show a system’s physical layout, revealing which pieces of software run on
what pieces of hardware. Deployment diagrams are really very simple; hence the short chapter.
Figure 8.1 is a simple example of a deployment diagram. The main items on the diagram are nodes
connected by communication paths. A node is something that can host some software. Nodes come
in two forms. A device is hardware, it may be a computer or a simpler piece of hardware connected
to a system. An execution environment is software that itself hosts or contains other software,
examples are an operating system or a container process.
The nodes contain artifacts, which are the physical manifestations of software: usually, files. These
files might be executables (such as .exe files, binaries, DLLs, JAR files, assemblies, or scripts), or
data files, configuration files, HTML documents, and so on. Listing an artifact within a node shows
that the artifact is deployed to that node in the running system.
You can show artifacts either as class boxes or by listing the name within a node. If you show them
as class boxes, you can add a document icon or the «artifact» keyword. You can tag nodes or
artifacts with tagged values to indicate various interesting information about the node, such as
vendor, operating system, location, or anything else that takes your fancy.
Often, you’ll have multiple physical nodes carrying out the same logical task. You can either show
this with multiple node boxes or state the number as a tagged value. In Figure 8.1, I used the tag
number deployed to indicate three physical Web servers, but there’s no standard tag for this.
Artifacts are often the implementation of a component. To show this, you can use a tagged value in
the artifact box.
Communication paths between nodes indicate how things communicate. You can label these paths
with information about the communication protocols that are used.
When to Use Deployment Diagrams
Don’t let the brevity of this chapter make you think that deployment diagrams shouldn’t be used.
They are very handy in showing what is deployed where, so any nontrivial deployment can make
good use of them.
State Diagram
State machine diagrams are a familiar technique to describe the behavior of a system. Various
forms of state diagrams have been around since the 1960s and the earliest object-oriented
techniques adopted them to show behavior. In object-oriented approaches, you draw a state
machine diagram for a single class to show the lifetime behavior of a single object.
Whenever people write about state machines, the examples are inevitably cruise controls or vending
machines. As I’m a little bored with them, I decided to use a controller for a secret panel in a Gothic castle. In this castle, I want to keep my valuables in a safe that’s hard to find. So to reveal the lock
to the safe, I have to remove a strategic candle from its holder, but this will reveal the lock only
while the door is closed. Once I can see the lock, I can insert my key to open the safe. For extra
safety, I make sure that I can open the safe only if I replace the candle first. If a thief neglects this
precaution, I’ll unleash a nasty monster to devour him.
Figure 10.1 shows a state machine diagram of the controller class that directs my unusual security
system.The state diagram starts with the state of the controller object when it’s created: in Figure
10.1, the Wait state. The diagram indicates this with initial pseudostate, which is not a state but
has an arrow that points to the initial state.
The diagram shows that the controller can be in three states: Wait, Lock, and Open. The diagram
also gives the rules by which the controller changes from state to state. These rules are in the form
of transitions: the lines that connect the states.
The transition indicates a movement from one state to another. Each transition has a label that
comes in three parts: trigger-signature [guard]/activity. All the parts are optional. The
trigger-signature is usually a single event that triggers a potential change of state. The guard, if
present, is a Boolean condition that must be true for the transition to be taken. The activity is
some behavior that’s executed during the transition. It may be any behavioral expression. The full
form of a trigger-signature may include multiple events and parameters. So in Figure 10.1, you
read the outward transition from the Wait state as “In the Wait state if the candle is removed
providing the door is open, you reveal the lock and move to the Lock state.”
All three parts to a transition are optional. A missing activity indicates that you don’t do anything
during the transition. A missing guard indicates that you always take the transition if the event
occurs. A missing trigger-signature is rare but does occur. It indicates that you take the transition
immediately, which you see mostly with activity states, which I’ll come to in a moment.
When an event occurs in a state, you can take only one transition out of it. So if you use multiple
transitions with the same event, as in the Lock state of Figure 10.1, the guards must be mutually
exclusive. If an event occurs and no transition is valid—for example, a safe-closed event in the Wait
state or a candle-removed event with the door closed—the event is ignored.
The final state indicates that the state machine is completed, implying the deletion of the controller
object. Thus, if someone should be so careless as to fall for my trap, the controller object
terminates, so I would need to put the rabbit in its cage, mop the floor, and reboot the system.
Remember that state machines can show only what the object directly observes or activates. So
although you might expect me to add or remove things from the safe when it’s open, I don’t put
that on the state diagram, because the controller cannot tell.
When developers talk about objects, they often refer to the state of the objects to mean the
combination of all the data in the fields of the objects. However, the state in a state machine
diagram is a more abstract notion of state; essentially, different states imply a different way of
reacting to events.
Internal Activities
States can react to events without transition, using internal activities: putting the event, guard,
and activity inside the state box itself.
Figure 10.2 shows a state with internal activities of the character and help events, as you might find
on a UI text field. An internal activity is similar to a self-transition: a transition that loops back to
the same state. The syntax for internal activities follows the same logic for event, guard, and
procedure.
Figure 10.2 also shows two special activities: the entry and exit activities. The entry activity is
executed whenever you enter a state; the exit activity, whenever you leave. However, internal
activities do not trigger the entry and exit activities; that is the difference between internal activities
and self-transitions.
Activity States
In the states I’ve described so far, the object is quiet and waiting for the next event before it does
something. However, you can have states in which the object is doing some ongoing work.
The Searching state in Figure 10.3 is such an activity state: The ongoing activity is marked with
the do/; hence the term do-activity. Once the search is completed, any transitions without an
activity, such as the one to display new hardware, are taken. If the cancel event occurs during the
activity, the do-activity is unceremoniously halted, and we go back to the Update Hardware Window
state.
Both do-activities and regular activities represent carrying out some behavior. The critical difference
between the two is that regular activities occur “instantaneously” and cannot be interrupted by
regular events, while do-activities can take finite time and can be interrupted, as in Figure 10.3.
Instantaneous will mean different things for different system; for hard real-time systems, it might
be a few machine instructions, but for desktop software might be several seconds.
UML 1 used the term action for regular activities and used activity only for do-activities.
Superstates
Often, you’ll find that several states share common transitions and internal activities. In these cases,
you can make them substates and move the shared behavior into a superstate, as in Figure 10.4.
Without the superstate, you would have to draw a cancel transition for all three states within the
Enter Connection Details state.
Concurrent States
States can be broken into several orthogonal state diagrams that run concurrently. Figure 10.5
shows a pathetically simple alarm clock that can play either CDs or the radio and show either the
current time or the alarm time.
The choices CD/radio and current/alarm time are orthogonal choices. If you wanted to represent this
with a nonorthogonal state diagram, you would need a messy diagram that would get very much out
of hand should you want more states. Separating out the two areas of behavior into separate state
diagrams makes it much clearer.
Figure 10.5 also includes a history pseudostate. This indicates that when the clock is switched on,
the radio/CD choice goes back to the state the clock was in when it was turned off. The arrow from
the history pseudostate indicates what state to be in on the first time when there is no history.
Implementing State Diagrams
A state diagram can be implemented in three main ways: nested switch, the State pattern, and
state tables. The most direct approach to handling a state diagram is a nested switch statement,
such as Figure 10.6. Although it’s direct, it’s long-winded, even for this simple case. It’s also very
easy for this approach to get out of control, so I don’t like using it even for simple cases.
The State pattern [Gang of Four] creates a hierarchy of state classes to handle behavior of the
states. Each state in the diagram has one state subclass. The controller has methods for each event,
which simply forwards to the state class. The state diagram of Figure 10.1 would yield an
implementation indicated by the classes of Figure 10.7.
The top of the hierarchy is an abstract class that implements all the event-handling methods to do
nothing. For each concrete state, you simply override the specific event methods for which that
state has transitions.
The state table approach captures the state diagram information as data. So Figure 10.1 might end
up represented in a table like Table 10.1. We then build either an interpreter that uses the state
table at runtime or a code generator that generates classes based on the state table.
Obviously, the state table is more work to do once, but then you can use it every time you have a
state problem to hold. A runtime state table can also be modified without recompilation, which in some contexts is quite handy. The state pattern is easier to put together when you need it, and
although it needs a new class for each state, it’s a small amount of code to write in each case.
These implementations are pretty minimal, but they should give you an idea of how to go about
implementing state diagrams. In each case, implementing state models leads to very boilerplate
code, so it’s usually best to use some form of code generation to do it.
When to Use State Diagrams
State diagrams are good at describing the behavior of an object across several use cases. State
diagrams are not very good at describing behavior that involves a number of objects collaborating.
As such, it is useful to combine state diagrams with other techniques. For instance, interaction
diagrams (see Chapter 4) are good at describing the behavior of several objects in a single use case,
and activity diagrams (see Chapter 11) are good at showing the general sequence of activities for
several objects and use cases.
Not everyone finds state diagrams natural. Keep an eye on how people are working with them. It
may be that your team does not find state diagrams useful to its way of working. That is not a big
problem; as always, you should remember to use the mix of techniques that works for you.
If you do use state diagrams, don’t try to draw them for every class in the system. Although this
approach is often used by high-ceremony completists, it is almost always a waste of effort. Use state
diagrams only for those classes that exhibit interesting behavior, where building the state diagram
helps you understand what is going on. Many people find that UI and control objects have the kind
of behavior that is useful to depict with a state diagram.
Sequence Diagram
Interaction diagrams describe how groups of objects collaborate in some behavior. The UML
defines several forms of interaction diagram, of which the most common is the sequence diagram.
Typically, a sequence diagram captures the behavior of a single scenario. The diagram shows a
number of example objects and the messages that are passed between these objects within the use
case.
To begin the discussion, I’ll consider a simple scenario. We have an order and are going to invoke a
command on it to calculate its price. To do that, the order needs to look at all the line items on the
order and determine their prices, which are based on the pricing rules of the order line’s products.
Having done that for all the line items, the order then needs to compute an overall discount, which
is based on rules tied to the customer.
Figure 4.1 is a sequence diagram that shows one implementation of that scenario. Sequence
diagrams show the interaction by showing each participant with a lifeline that runs vertically down
the page and the ordering of messages by reading down the page.
One of the nice things about a sequence diagram is that I almost don’t have to explain the notation.
You can see that an instance of order sends getQuantity and getProduct messages to the order
line. You can also see how we show the order invoking a method on itself and how that method
sends getDiscountInfo to an instance of customer.
The diagram, however, doesn’t show everything very well. The sequence of messages getQuantity,
getProduct, getPricingDetails, and calculateBasePrice needs to be done for each order line on
the order, while calculateDiscounts is invoked just once. You can’t tell that from this diagram,
although I’ll introduce some more notation to handle that later.
Most of the time, you can think of the participants in an interaction diagram as objects, as indeed
they were in UML 1. But in UML 2, their roles are much more complicated, and to explain it all fully
is beyond this book. So I use the term participants, a word that isn’t used formally in the UML spec. In UML 1, participants were objects and so their names were underlined, but in UML 2, they
should be shown without the underline, as I’ve done here.
In these diagrams, I’ve named the participants using the style anOrder. This works well most of the
time. A fuller syntax is name : Class, where both the name and the class are optional, but you
must keep the colon if you use the class. (Figure 4.4, shown on page 58, uses this style.)
Each lifeline has an activation bar that shows when the participant is active in the interaction. This
corresponds to one of the participant’s methods being on the stack. Activation bars are optional in
UML, but I find them extremely valuable in clarifying the behavior. My one exception is when
exploring a design during a design session, because they are awkward to draw on whiteboards.
Naming often is useful to correlate participants on the diagram. The call getProduct is shown
returning aProduct, which is the same name, and therefore the same participant, as the aProduct
that the getPricingDetails call is sent to. Note that I’ve used a return arrow for only this call; I
did that to show the correspondance. Some people use returns for all calls, but I prefer to use them
only where they add information; otherwise, they simply clutter things. Even in this case, you could
probably leave the return out without confusing your reader.
The first message doesn’t have a participant that sent it, as it comes from an undetermined source.
It’s called a found message.
For another approach to this scenario, take a look at Figure 4.2. The basic problem is still the same,
but the way in which the participants collaborate to implement it is very different. The Order asks
each Order Line to calculate its own Price. The Order Line itself further hands off the calculation to
the Product; note how we show the passing of a parameter. Similarly, to calculate the discount, the
Order invokes a method on the Customer. Because it needs information from the Order to do this,
the Customer makes a reentrant call (getBaseValue) to the Order to get the data.
The first thing to note about these two diagrams is how clearly the sequence diagram indicates the
differences in how the participants interact. This is the great strength of interaction diagrams. They
aren’t good at showing details of algorithms, such as loops and conditional behavior, but they make
the calls between participants crystal clear and give a really good picture about which participants
are doing which processing.
The second thing to note is the clear difference in styles between the two interactions. Figure 4.1 is
centralized control, with one participant pretty much doing all the processing and other
participants there to supply data. Figure 4.2 uses distributed control, in which the processing is
split among many participants, each one doing a little bit of the algorithm.
Both styles have their strengths and weaknesses. Most people, particularly those new to objects, are
more used to centralized control. In many ways, it’s simpler, as all the processing is in one place;
with distributed control, in contrast, you have the sensation of chasing around the objects, trying to
find the program.
Despite this, object bigots like me strongly prefer distributed control. One of the main goals of good
design is to localize the effects of change. Data and behavior that accesses that data often change
together. So putting the data and the behavior that uses it together in one place is the first rule of
object-oriented design.
Furthermore, by distributing control, you create more opportunities for using polymorphism rather
than using conditional logic. If the algorithms for product pricing are different for different types of
product, the distributed control mechanism allows us to use subclasses of product to handle these
variations.
In general the OO style is to use a lot of little objects with a lot of little methods that give us a lot of
plug points for overriding and variation. This style is very confusing to people used to long
procedures; indeed, this change is the heart of the paradigm shift of object orientation. It’s
something that’s very difficult to teach. It seems that the only way to really understand it is to work
in an OO environment with strongly distributed control for a while. Many people then say that they
get a sudden “aha” when the style makes sense. At this point, their brains have been rewired, and
they start thinking that decentralized control is actually easier.
Creating and Deleting Participants
Sequence diagrams show some extra notation for creating and deleting participants (Figure 4.3). To
create a participant, you draw the message arrow directly into the participant box. A message name
is optional here if you are using a constructor, but I usually mark it with “new” in any case. If the
participant immediately does something once it’s created, such as the query command, you start an
activation right after the participant box.
Deletion of a participant is indicated by big X. A message arrow going into the X indicates one
participant explicitly deleting another; an X at the end of a lifeline shows a participant deleting itself.
In a garbage-collected environment, you don’t delete objects directly, but it’s still worth using the X
to indicate when an object is no longer needed and is ready to be collected. It’s also appropriate for
close operations, indicating that the object isn’t usable any more.
Loops, Conditionals, and the Like
A common issue with sequence diagrams is how to show looping and conditional behavior. The first
thing to point out is that this isn’t what sequence diagrams are good at. If you want to show control
structures like this, you are better off with an activity diagram or indeed with code itself. Treat
sequence diagrams as a visualization of how objects interact rather than as a way of modeling
control logic.
That said, here’s the notation to use. Both loops and conditionals use interaction frames, which
are ways of marking off a piece of a sequence diagram. Figure 4.4 shows a simple algorithm based
on the following pseudocode:
procedure dispatch
foreach (lineitem)
if (product.value > $10K)
careful.dispatch
else
regular.dispatch
end if
end for
if (needsConfirmation) messenger.confirm
end procedure
In general, frames consist of some region of a sequence diagram that is divided into one or more
fragments. Each frame has an operator and each fragment may have a guard. (Table 4.1 lists
common operators for interaction frames.) To show a loop, you use the loop operand with a single
fragment and put the basis of the iteration in the guard. For conditional logic, you can use an alt
operator and put a condition on each fragment. Only the fragment whose guard is true will execute.
If you have only one region, there is an opt operator.
Interaction frames are new in UML 2. As a result, you may see diagrams prepared before UML 2 and
that use a different approach; also, some people don’t like the frames and prefer some of the older
conventions. Figure 4.5 shows some of these unofficial tweaks.
UML 1 used iteration markers and guards. An iteration marker is a * added to the message name.
You can add some text in square brackets to indicate the basis of the iteration. Guards are a
conditional expression placed in square brackets and indicate that the message is sent only if the
guard is true. While these notations have been dropped from sequence diagrams in UML 2, they are
still legal on communication diagrams.
Although iteration markers and guards can help, they do have weaknesses. The guards can’t
indicate that a set of guards are mutually exclusive, such as the two on Figure 4.5. Both notations
work only with a single message send and don’t work well when several messages coming out of a
single activation are within the same loop or conditional block.
To get around this last problem, an unofficial convention that’s become popular is to use a
pseudomessage, with the loop condition or the guard on a variation of the self-call notation. In
Figure 4.5, I’ve shown this without a message arrow; some people include a message arrow, but leaving it out helps reinforce that this isn’t a real call. Some also like to gray shade the
pseudomessage’s activation bar. If you have alterative behavior, you can show that with an
alternative marker between the activations.
Although I find activations very helpful, they don’t add much in the case of the dispatch method,
whereby you send a message and nothing else happens within the receiver’s activation. A common
convention that I’ve shown on Figure 4.5 is to drop the activation for those simple calls.
The UML standard has no graphic device to show passing data; instead, it’s shown by parameters in
the message name and return arrows. Data tadpoles have been around in many methods to
indicate the movement of data, and many people still like to use them with the UML.
All in all, although various schemes can add notation for conditional logic to sequence diagrams, I
don’t find that they work any better than code or at least pseudocode. In particular, I find the
interaction frames very heavy, obscuring the main point of the diagram, so I prefer
pseudomessages.
Synchronous and Asynchronous Calls
If you’re exceptionally alert, you’ll have noticed that the arrowheads in the last couple of diagrams
are different from the arrowheads earlier on. That minor difference is quite important in UML 2. In
UML 2, filled arrowheads show a synchronous message, while stick arrowheads show an
asynchronous message.
If a caller sends a synchronous message, it must wait until the message is done, such as invoking
a subroutine. If a caller sends an asynchronous message, it can continue processing and doesn’t
have to wait for a response. You see asynchronous calls in multithreaded applications and in
message-oriented middleware. Asynchrony gives better responsiveness and reduces the temporal
coupling but is harder to debug.
The arrowhead difference is very subtle; indeed, rather too subtle. It’s also a backward-incompatible
change introduced in UML 1.4, before then an asynchronous message was shown with the half-stick
arrowhead, as in Figure 4.5.
I think that this arrowhead distinction is too subtle. If you want to highlight asynchronous messages,
I would recommend using the obsolete half-stick arrowhead, which draws the eye much better to an
important distinction. If you’re reading a sequence diagram, beware of making assumptions about
synchrony from the arrowheads unless you’re sure that the author is intentionally making the
distinction.
When to Use Sequence Diagrams
You should use sequence diagrams when you want to look at the behavior of several objects within
a single use case. Sequence diagrams are good at showing collaborations among the objects; they
are not so good at precise definition of the behavior.
If you want to look at the behavior of a single object across many use cases, use a state diagram
(see Chapter 10). If you want to look at behavior across many use cases or many threads, consider
an activity diagram (see Chapter 11).
If you want to explore multiple alternative interactions quickly, you may be better off with CRC
cards, as that avoids a lot of drawing and erasing. It’s often handy to have a CRC card session to
explore design alternatives and then use sequence diagrams to capture any interactions that you
want to refer to later.
Other useful forms of interaction diagrams are communication diagrams, for showing connections;
and timing diagrams, for showing timing constraints.
Activity diagrams
Activity diagrams are a technique to describe procedural logic, business process, and work flow. In
many ways, they play a role similar to flowcharts, but the principal difference between them and
flowchart notation is that they support parallel behavior.
Activity diagrams have seen some of the biggest changes over the versions of the UML, so they
have, not surprisingly, been significantly extended and altered again for UML 2. In UML 1, activity diagrams were seen as special cases of state diagrams. This caused a lot of problems for people
modeling work flows, which activity diagrams are well suited for. In UML 2, that tie was removed.
Figure 11.1 shows a simple example of an activity diagram. We begin at the initial node action and
then do the action Receive Order. Once that is done, we encounter a fork. A fork has one incoming
flow and several outgoing concurrent flows.
Figure 11.1 says that Fill Order, Send Invoice, and the subsequent actions occur in parallel.
Essentially, this means that the sequence between them is irrelevant. I could fill the order, send the
invoice, deliver, and then receive payment; or, I could send the invoice, receive the payment, fill the
order, and then deliver: You get the picture.
I can also do these actions by interleaving. I grab the first line item from stores, type up the invoice,
grab the second line item, put the invoice in an envelope, and so forth. Or, I could do some of this simultaneously: type up the invoice with one hand while I reach into my stores with another. Any of
these sequences is correct, according to the diagram.
The activity diagram allows whoever is doing the process to choose the order in which to do things.
In other words, the diagram merely states the essential sequencing rules I have to follow. This is
important for business modeling because processes often occur in parallel. It’s also useful for
concurrent algorithms, in which independent threads can do things in parallel.
When you have parallelism, you’ll need to synchronize. We don’t close the order until it is delivered
and paid for. We show this with the join before the Close Order action. With a join, the outgoing
flow is taken only when all the incoming flows reach the join. So you can close the order only when
you have both received the payment and delivered.
UML 1 had particular rules for balancing the forks and joins, as activity diagrams were special cases
of state diagrams. With UML 2, such balancing is no longer needed.
You’ll notice that the nodes on an activity diagram are called actions, not activities. Strictly, an
activity refers to a sequence of actions, so the diagram shows an activity that’s made up of actions.
Conditional behavior is delineated by decisions and merges. A decision, called branch in UML 1, has
a single incoming flow and several guarded out-bound flows. Each outbound flow has a guard: a
Boolean condition placed inside square brackets. Each time you reach a decision, you can take only
one of the outbound flows, so the guards should be mutually exclusive. Using [else] as a guard
indicates that the [else] flow should be used if all the other guards on the decision are false.
In Figure 11.1, after an order is filled, there is a decision. If you have a rush order, you do an
Overnight Delivery; otherwise, you do a Regular Delivery.
A merge has multiple input flows and a single output. A merge marks the end of conditional
behavior started by a decision.
In my diagrams, each action has a single flow coming in and a single flow going out. In UML 1,
multiple incoming flows had an implicit merge. That is, your action would execute if any flow
triggered. In UML 2, this has changed so there’s an implicit join instead; thus, the action executes
only if all flows trigger. As a result of this change, I recommend that you use only a single incoming
and outgoing flow to an action and show all joins and merges explicitly; that will avoid confusion.
Decomposing an Action
Actions can be decomposed into subactivities. I can take the delivery logic of Figure 11.1 and define
it as its own activity (Figure 11.2). Then I can call it as an action (Figure 11.3 on page 121).
Actions can be implemented either as subactivities or as methods on classes. You can show a
subactivity by using the rake symbol. You can show a call on a method with syntax class-
name::method-name. You can also write a code fragment into the action symbol if the invoked
behavior isn’t a single method call.
And There’s More
I should stress that this chapter only scratches the surface on activity diagrams. As with so much of
the UML, you could write a whole book on this one technique alone. Indeed, I think that activity
diagrams would make a very suitable topic for a book that really dug into the notation and how to
use it.
The vital question is how widely they get used. Activity diagrams aren’t the most widely used UML
technique at the moment, and their flow-modeling progenitors weren’t very popular either.
Diagrammatic techniques haven’t yet caught on much for describing behavior in this kind of way. On
the other hand, there are signs in a number of communities of a pent-up demand that a standard
technique will help to satisfy.
When to Use Activity Diagrams
The great strength of activity diagrams lies in the fact that they support and encourage parallel
behavior. This makes them a great tool for work flow and process modeling, and indeed much of the
push in UML 2 has come from people involved in work flow.
You can also use an activity diagram as a UML-compliant flowchart. Although this allows you to do
flowcharts in a way that sticks with the UML, it’s hardly very exciting. In principle, you can take
advantages of the forks and joins to describe parallel algorithms for concurrent programs. Although
I don’t travel in concurrent circles that much, I haven’t seen much evidence of people using them
there. I think the reason is that most of the complexity of concurrent programming is in avoiding
contention on data, and activity diagrams don’t help much with that.
The main strength of doing this may come with people using UML as a programming language. In
this case, activity diagrams represent an important technique to represent behavioral logic.
I’ve often seen activity diagrams used to describe a use case. The danger of this approach is that
often, domain experts don’t follow them easily. If so, you’d be better off with the usual textual form.