2. Class Diagrams
Class diagrams are
probably the most widely used UML diagram. You don’t have to be a a huge
fan of UML, or an experienced UML user, to have seen and to recognize a
class diagram. A class diagram represents the static structure of a
system. The static structure of a system is composed of classes and
their relationships. Classes are exactly the classes (and interfaces) to
be implemented by the development team. Let’s start with a look at the
notation.
2.1. A Look at the Notation
From a notational
perspective, the class is a rectangle that is usually vertically
partitioned in three areas. The top-most rectangle contains the name of
the class. The second area lists the attributes
of the class. By attributes, we mean properties and fields you want to
see defined in the body of the class. Finally, the bottom area contains
class operations. By operations, we mean essentially class methods. (See Table 1.)
Table 1. A class description in a class diagram
Table |
---|
+Name:string |
+Clear() |
+WriteToFile(in fileName:string):int |
The name of the class
can also be qualified by a namespace. In UML, the concept of a namespace
(as we know it from the Microsoft .NET Framework) is expressed through
the package. To indicate that a class should be defined within a
package, you concatenate the names using double colons (::)—for example,
System.Data::DataTable.
Characteristics of the class, attributes, and operations are associated with a modifier. Feasible symbols are listed in Table 2.
Table 2. UML Modifiers
Symbol | Meaning |
---|
+ | The attribute or operation is public. |
- | The attribute or operation is private. |
# | The attribute or operation is protected. |
In Table 2, you see a sample class Table with a property Name and a couple of methods: Clear and WriteToFile. All properties and methods are public.
Note that when you define a UML class, you also specify the type of a property and the signature of a method. The class Table schematized in Table 2-8 can be expressed in pure C# as follows:
class Table
{
public string Name;
public void Clear()
{
}
public int WriteToFile(string fileName)
{
}
}
Special font styles
indicate a special meaning, too. In particular, if you use the Italic
style with an attribute or operation, you indicate you want it to be a
virtual member of the class. The Underlined style indicates a static
member.
Finally, UML supplies a specific symbol to refer to parameterized types—that
is, .NET generic types. The symbol is similar to a conventional class
and includes an extra overlapped dashed rectangle with a placeholder for
types. (See Figure 7.)
Let’s delve a bit deeper into the syntax and sematics of attributes and relationships.
Like
it or not, UML is a bit biased by the Java language. You might have
suspected this already from the aforementioned use of the term packagenamespace. But there’s more to the story. instead of
According to UML and
the Java language, class attributes should be limited to defining
fields. So what about properties? In Java, you don’t even have
properties; in the .NET Framework, properties are a pinch of syntactic
sugar. As you might know, in the .NET Framework properties are
implemented through a pair of getter and setter methods. So UML suggests that you use the same approach for properties.
This is the theory;
but what about the practice? In our projects, we use attributes for
both properties and fields. We employ a sort of de facto standard
notation to distinguish fields from properties. Property names are PascalCase, whereas field names are camelCase. And in general, everything we mark as public is a property.
Likewise, in both UML
and the Java language you don’t have events. In Java, a different
eventing model is used based on your own implementation of the
publish/subscribe pattern. In the .NET Framework, events are merely a
system-provided implementation of the same pattern, made even simpler by
idiomatic language features such as the event keyword in C#.
So in UML, you have no
syntax element to define an event as a class attribute. Yet, our .NET
classes might require events. What do we do? We simply use attributes of
type EventHandler or any other more specific event delegate type. Developers will understand.
|
2.2. Attributes
As mentioned,
attributes indicate fields of a class, but in a pure .NET context we
loosely use them to define properties and events, too. Attributes are
made of a line of text. The full notation includes the following:
Access modifier.
Name of the attribute.
Type of the attribute. The type can be either a primitive type or a custom type.
Multiplicity of the attribute—that is, the number of objects used to define a value for the attribute.
Default value (if any) for the attribute.
Here’s a more complete definition for the Name attribute in a Table class:
+ Name: String = String.Empty
This UML definition might become the following C# code:
public string Name = String. Empty;
The multiplicity is
a single number, or an interval, that indicates how many values are
required to set the attribute. Numbers appear in square brackets. A
single number indicates that a single value or instance is required. The
previous example can be rewritten as shown here:
+ Name: String [1] = string.Empty
A collection of values is represented with the following notation:
+ Rows: Row [0..5]
In this case, to initialize the Rows attribute, up to six instances of the Row object are required. In C#, you might have the following:
public Row[6] Rows;
You use the asterisk (*) symbol to indicate an unlimited number, as shown here:
+ Rows: Row[0..*] = List<Row>
This corresponds to the following C# code:
public IList<Row> Rows = new List<Row>();
2.3. Operations
In general, operations in a
UML class diagram indicate the actions that instances of a class can
take. Operations are essentially methods. Here’s the full notation:
Each parameter in the list is represented with a notation close to that of attributes, but with an extra prefix, as shown in Table 3.
Table 3. Direction of a Parameter
Prefix | Meaning |
---|
In | Input parameter |
Out | Output parameter |
Inout | Input and output parameter |
If unspecified, the direction is assumed to be in. Look at the following operation:
+ WriteToFile(fileName: string, overwriteIfExisting: bool): int
It corresponds to the following C# code:
public int WriteToFile(string filename, bool overwriteIfExisting)
{
}
The UML’s out prefix corresponds to the C#’s out keyword, whereas UML’s inout matches C#’s ref keyword. Note that not all languages can accommodate this. Visual Basic .NET. for example, can’t deal with UML’s out prefix directly. To define an output parameter in Visual Basic .NET you have to use the keyword ByRef, which actually corresponds to UML’s inout. In the end, in Visual Basic .NET an output parameter always has to be promoted to an input and output parameter.
2.4. Associations
An association indicates a
relationship existing between classes. You can use an association as an
alternative syntax to list the properties of a class. In terms of
notation, an association is a solid line between classes. Table 4 and Figure 8 express equivalent content.
Table 4. Attributes in the Order class are expressed with a simple notation.
Order |
---|
+Customer:Customer
+Items[1..*]:OrderItem |
|
With
associations, you can indicate the multiplicity at both ends, and this
might make the resulting diagram clearer to read. Because they are
logically equivalent, the diagrams in both Table 4 and Figure 8 can be expressed with the following C# code:
class Order
{
public Customer Customer;
public List<OrderItem> Items;
}
Two special types of associations are aggregation and composition.
Aggregation specifies a whole/part relationship between two objects, or in other words a has-a relationship. Graphically, it is represented using a connecting line ending with a clear diamond shape on the container class. Figure 9 shows an aggregation where the Order class has a Customer using a one-to-one multiplicity.
Note that in the
case of aggregation the contained object is not entirely dependent on
the container. If you destroy the container, this might not affect the
contained objects. In Figure 9, Order has one and only one Customer, but if the Customer disappears (perhaps goes out of business), the Order remains (perhaps as historical information).
Composition
is perhaps a stronger form of aggregation. It specifies that you have a
compound object where the container entirely owns the contained object.
If you destroy the parent, the children are destroyed as well. You
indicate a composition using a connecting line ending with a black
diamond shape on the container class. The line can be adorned with
multiplicity. (See Figure 10.)
2.5. Generalization
The generalization
relationship occurs between two classes and indicates that one
specializes the other, thus making it a subtype. As you can see, this
relationship corresponds to inheritance in C# and other object-oriented
languages. The relationship is rendered using a line that ends with a
hollow triangle. The line connects the involved classes, and the
triangle touches on the parent class, as illustrated in Figure 11.
Generalization defines a relationship of type is-a. With an eye on Figure 11; therefore, we can say that "supplier is a company" and, likewise, that "customer is a company."
2.6. Dependency
A
dependency relationship exists between two classes when one class
requires another class in order to be implemented. You have a dependency
when a change in one class affects the other class.
The notation for a
dependency is a dashed line going from the dependent class to the
independent class. The line ends with an open arrow touching on the
independent class. For example, you might have a line connecting a
client to a supplier. This indicates a dependency of the client on the
supplier. If you modify the supplier, the client might be broken. (See Figure 12.)
Note
Many UML
relationships imply a dependency. For example, a class derived from a
parent class is dependent on its base class. Although dependencies
between classes (static dependencies) are easy to discover at design
time, other forms of dependencies (such as transient relationships)
require code inspection or analysis to be discovered.