UML Class Diagrams
Why We Use UML
You could create a complicated system entirely on your own, keeping all the details straight in your mind. It’s been known to happen. At Amazon, you work as part of a team where people work on different parts of a large system, humans misinterpret both verbal and written instructions, and we find it difficult to keep lots of details in our heads. It’s important to figure out what you’re going to do before you start doing it. We have found that pictures help us think about complicated problems because they can convey information quickly and help us identify patterns.
When we apply rules to a picture to make it precise as well, we call it a diagram. The Unified Modeling Language (UML) is a ruleset that makes it possible for us to describe large, complicated systems in precise yet easily understood ways.
More than 25 years ago, when Object Oriented programming was still revolutionary, Putnam Texel was fond of saying “This is not the THE way, it’s A way.” This documentation demonstrates A way that you can adapt to your own needs. It only covers the UML features most commonly encountered for communicating about software; if you want an authoritative reference, the Object Management Group keeps specification documents online.
Tools
There are lots of UML drawing tools. NSS uses PlantUML, a tool that turns simple text descriptions into diagrams. This makes development fast, although we lose the ability to draw shapes exactly where we want them. You can use PlantUML to create diagrams in the https://www.planttext.com/ online tool as well as in many IDEs such as Visual Studio Code and IntelliJ. No matter where you create your diagrams, you can submit the PlantUML text to complete any assigned diagramming task.
Class Diagrams
Class Diagrams show the overall structure of a software program or system by describing its classes and their relationships. This makes it easier to understand how the system works. Class Diagrams do not explain how a system manipulates data or how any specific problem in the system is solved.
When you draw a diagram, be conscious of the amount of detail you include. You want to describe the software, not program it. Diagrams are for conceptualizing and communicating. The diagram should help you discover details you hadn’t considered, but as you implement the program you’ll inevitably discover that you need to go back and change the diagram.
Consider an Amazon purchase order like the one below. Every order has a list of line items. Each line item includes an item and a quantity. The order is fulfilled from a particular warehouse.
Line | ASIN | Description | Quantity | Price | Subtotal |
---|---|---|---|---|---|
1 | B0754RBQKX | Happy Fun Ball T-Shirt | 1 | $18.991 | $18.99 |
2 | 1539486125 | The Farting Animal Coloring Book | 1 | $5.99 | $5.99 |
3 | B0045C9NO8 | Smiffy’s Men’s Turkey Hat | 4 | $11.99 | $47.96 |
Total | $72.94 |
Let’s use a UML diagram to describe how an Order is built at Amazon. We’ll diagram three classes (Order
, LineItem
, and Item
), one enumeration (Warehouse
), their quantities, and relationships.
Classes
A class is represented by a box with three sections. The top section contains the class’s name. It may optionally include other decorations (such as the circle in the following example) to make classes distinguishable from other kinds of objects. The middle section contains the class’s attributes, and specifies their names, types, and visibility (whether they are public or private). The third section contains the class’s methods, and specifies their names, arguments and argument types, return types, and visibility.
Java
public class Order {
private String id;
private LineItem[] lines;
public String getId() {
return id;
}
public BigDecimal subtotalForLine(int lineNumber) {
final LineItem line = lines[lineNumber];
final BigDecimal quantity = new BigDecimal(line.getQuantity());
return quantity.multiply(line.getItem().getPrice());
}
public Warehouse findWarehouse() {
// Complicated logic based on inventory, delivery location,
// tax requirements, and who-knows-what-else goes here
}
}
Keep in mind that this is example code. It is not ready for production.
Class Diagram
These two diagrams both describe an Order in the same way, despite their varying appearance.
Do not include private methods in class diagrams; they clutter the diagram and make it difficult to see how the software works overall. Likewise, “getters” (methods that simply return an attribute’s value) should also be excluded from class diagrams, as in this example. We usually leave out “setters” (methods that simply set an attribute’s value), but if the argument is a different type or requires modification, we usually include them.
Common Variations
- Different colors and shapes are common, depending on the tool used to create the diagram.
- The red box for “private” and green circle for “public” are fairly common, but some tools use characters instead. In these cases, you’ll see
-
for private and+
for public. - Some teams include private methods like validate() to emphasize important internal activities
- Additional declarations and specifications which will become intuitive as you learn more
PlantUML
class Order {
- id : String
- lines : LineItem[]
+ subtotalForLine(lineNumber : int) : BigDecimal;
+ findWarehouse() : Warehouse
}
- Specify public attributes and methods by prepending
+
. Use-
for private ones. - For attributes use
name : type
- For methods use
name(arg1 : type1, arg2 : type2) : ReturnType
You can put methods and attributes in any order, but you should group them in sections just like in the diagram.
Enumerations
Enumerations are drawn just like classes, but include a decoration called a “stereotype” to indicate that they’re a special kind of class. Stereotypes are a description written between guillemets (<<
and >>
).
Java
public enum Warehouse {
CLT2(28214),
MCO1(32827),
MCO5(33897),
PDX5(97124),
PDX9(98038),
RDU5(27703),
SEA8(98004);
private int zip;
private Warehouse(int zip) {
this.zip = zip;
}
public int getZip() {
return zip;
}
}
Class Diagram
Both of these class diagrams describe an enumeration in the same way, despite their varying appearance.
Unlike classes, enumerations should include constructors and getters if they have them; this helps to clarify the meaning of specific values. The values used in the constructor are specified after a colon, where the attribute type would be in a regular class.
Common Variations
- The stereotype
<<enum>>
instead of<<enumeration>>
- Enumerations displayed in different colors or shapes than other classes
- Border style changes, such as thick or dotted borders
- Icons or other decorations, similar to the circled uppercase “E” in the example
- Constructor values in other formats (following an equals sign, surrounded by
<>
, etc)
PlantUML
enum Warehouse<<enumeration>> {
+ CLT2 : 28214
+ MCO1 : 32827
+ MCO5 : 33897
+ PDX5 : 97124
+ PDX9 : 98038
+ RDU5 : 27703
+ SEA8 : 98004
- Warehouse(zip : int)
+ getZip() : int
}
Specify that a class is an enumeration by substituting enum
instead of the keyword class
, and adding the stereotype between double angle brackets (<<enumeration>>
) after the enum name.
Note that enumeration values should always be public and uppercase.
Methods can be added for enumerations in exactly the same way as for other classes.
Composition and Aggregation
We build programs and classes out of many smaller, simpler classes. This mirrors the way objects in real life are composed. Developers often refer to this as the “has-a” relationship. In our example, every Order “has-a” collection of LineItems, and every LineItem “has-a” single Item.
Although we used the same phrase to describe both relationships, they are slightly different.
The Order and its LineItems are inextricably linked. The Order controls the LineItems. Orders are probably created at the same time as their LineItems. We don’t use the same LineItem in multiple Orders, or move them from one Order to another. If an Order is cancelled, we would cancel all its LineItems, too. You probably change the LineItems by calling a method in Order. We could say an Order “is-made-of” LineItems, or that a LineItem “belongs-to” an Order. The descriptions “is-part-of” or “owns” would also fit. We consider this composition.
On the other hand, the Item was probably created long before the LineItem existed. There may be many LineItems that refer to the same Item. If a LineItem is canceled, the Item persists. It’s not reasonable to say that the LineItem “owns” an Item. We could reasonably say that a LineItem “has-a” Item, or “refers-to” an Item. We consider this aggregation.
Java
// We've already described the Order class, so we're leaving it out
// to keep the code short.
public class LineItem {
private Item item;
private int quantity;
public Item getItem() {
return item;
}
public int getQuantity() {
return quantity;
}
}
public class Item {
private String asin;
private BigDecimal price;
public String getAsin() { return asin; }
public BigDecimal getPrice() { return price; }
}
Note that this code isn’t ready for production, either.
Class Diagram
The classes are exactly what you would expect from our previous example. Now we’ve connected them with lines describing their relationships.
The filled diamond indicates composition. In this case, an Order “is-made-of” LineItems.
The open diamond indicates aggregation. In this case, a LineItem “refers-to” an Item. The label on the line is completely optional, and is only meant to assist the reader’s comprehension.
We’ve also added numbers to the ends of the lines to indicate multiplicity or cardinality. The “1..” means “one to anything”, which could also be stated as “one or more”. You may also see “0..” for “zero or more”.
All in all, this is A way to say “every order owns at least one LineItem, and every LineItem refers to exactly one Item”.
Common Variations
- No multiplicities / cardinalities on the lines
- Single-character multiplicities: “+” for “at least one”, and “*” for “zero or more”
- No difference between filled and open diamonds.
- The attribute name may be removed from the class and used as the line label.
PlantUML
class Item {
- asin : String
- price : BigDecimal
}
class LineItem {
- item : Item
- quantity : int
}
class Order {
- id : String
- lines : LineItem[]
+ subtotalForLine(lineNumber : int) : BigDecimal
+ findWarehouse() : Warehouse
}
Order "1" *-- "1..*" LineItem
LineItem "1" o- "1" Item : "refers to"
Create lines with dashes (-
). Place multiplicity / cardinality in a string before and after the line. Use an asterisk (*
) to draw a closed diamond for composition. Use a lowercase letter o
(“oh”) to draw an open diamond for aggregation.
To guide the user to a particular interpretation of the relationship, add a label by appending a colon (:
) and a descriptive string.
Often PlantUML will just do “the right thing” and make a pretty diagram. Sometimes… not so much. You can provide a couple of hints to request different processing. Note that PlantUML will still draw what it considers the best picture, so your changes may not have much effect.
Don’t worry about it too much: if the submitted text should make a correct picture, we accept it as correct.
Ways to fudge your PlantUML picture (don’t spend more than 5 minutes trying these things):
- Use double dashes (
--
) to request a vertical line. - Use single dashes (
-
) to request a horizontal line. - Add spaces to strings to move them away from edges.
- Add a label to separate overlapping cardinalities
- Change the order of classes or relationships to change the rendering.
- Swap the direction of the relationship:
Order *-- LineItem
is the same asLineItem --* Order
, but it might be drawn differently.
Relationship
In our example, an Order may be fulfilled from a particular Warehouse, but the Order doesn’t “own” the Warehouse, nor is the Warehouse “part-of” the Order. The strongest connection we can make is that the Order “uses”, “knows about”, or “is related to” a Warehouse. This is simply called a relationship, or sometimes “plain relationship” or “uses relationship”.
Java
As seen previously, the Order has a findWarehouse()
method that returns a Warehouse. This is an example of a relationship. Another example would be a method that used a class in a parameter, but didn’t store the value in an attribute: for instance, a Time
class might have an add(Duration period)
method. In that case, Time
has a plain relationship with Duration
.
public class Order {
// We've already seen this class; the important point is that
// Order uses Warehouse, but doesn't "own" or "have-a" Warehouse
public Warehouse findWarehouse() {
// Complicated logic based on inventory, delivery location,
// tax requirements, and who-knows-what-else goes here
}
}
Class Diagram
We represent plain relationships with a line, labeled with a description of the relationship, but without any diamonds.
Common Variations
- Arrowheads on the ends of relationship lines, specifying which class “knows about” the other. In our example, since Order knows about Warehouse but not the other way around, there could be an arrowhead pointing to the Warehouse.
- Dotted or dashed lines for relationships.
- No label describing the relationship.
PlantUML
class Item {
- asin : String
- price : BigDecimal
}
class LineItem {
- item : Item
- quantity : int
}
class Order {
- id : String
- lines : LineItem[]
+ subtotalForLine(lineNumber : int) : BigDecimal
+ findWarehouse() : Warehouse
}
enum Warehouse<<enumeration>> {
+ CLT2 : 28214
+ MCO1 : 32827
+ MCO5 : 33897
+ PDX5 : 97124
+ PDX9 : 98038+ RDU5 : 27703
+ SEA8 : 98004
- Warehouse(zip : int)
+ getZip() : int
}
Order "1" *-- "1..*" LineItem
LineItem "1" o- "1" Item : "refers to"
Order - Warehouse : fulfilled by
This is the PlantUML for the complete diagram.