In this module, we’ll look at UML Class Diagrams. Class diagrams are a means of communicating design plan and structure. Note that there are many conventions in diagrams. In general, UML Class Diagram rules are not commandments describing an exact structure, but rather a set of general guidelines and common practices. Ultimately, the best diagram is one that aids in communication.
Image credit of Visual Paradigm.
UML Diagrams class overview: https://www.uml-diagrams.org/class-diagrams-overview.html
Tutorial that follows class lecture: https://www.edrawsoft.com/article/class-diagram-relationships.html
Before we go further, this article is very closely related with our discussion on Class Relationships in an earlier module, so be sure you are familiar with that content.
When describing an individual class, we will often use a structure like this:
Here, the class name is at the top. It may be indicated as <<abstract>>
or <<interface>>
when appropriate it is describing an abstract class
or interface
.
In the middle, fields are listed by the format [name]:[type]
. (A note that Kotlin and Typescript both use this naming scheme for their variables.)
A common practice you will see is to not include aggregation, composition, or association fields in the set of fields. Rather, those relationships are described by the relationship. For example, in the image at the top of this document, you’ll notice that the pane for Order
doesn’t include a List<OrderDetail>
, but the diagram clearly communicates that each Order
contains multiple OrderDetail
objects.
(As an aside, I don’t have a strong opinion on this practice: I don’t think it hurts to have the fields listed. For the course, either approach is fine, I only put this detail out so that when you see it you understand it)
In the bottom section, the methods are listed, along with their parameters and return types (again using the [name]:[type]
format). The constructors are always listed first.
You’ll notice some plus and minus signs. As a common practice:
+
- public
items (generally, any methods and constructors)-
- private
items (generally, fields)#
- protected
itemsA note, however, is that while listing private
fields makes sense, since it tells us the structure of the object and hints at it’s purpose and usage, listing private
methods (generally “helper” methods) doesn’t. This is because a private
method is an implementation detail that doesn’t influence how the class should behave.
A simplified UML Diagram simply lists the classes, but doesn’t include fields or method names.
Here we will show how relationships between classes are diagrammed. For this class, I want to focus on 6 relationship types in particular:
Dependency is the weakest form of coupling, where one class uses another, but does not “contain” another. The arrow points towards the class being used.
For instance, consider ClosestCenterStateFinder
below
public class ClosestCenterStateFinder implements StateFinder {
private List<State> states;
public State findState(Tweet t) {
Coordinate c = t.getCoordinates();
State closest = null;
for (State state : state) {
...
}
return closest;
}
}
Here, ClosestCenterStateFinder
is using Tweet and Coordinate. It uses Tweet
due to the findState function taking in Tweet
, and it uses Coordinate
because it interacts with that class by extracting it from the Tweet
.
(Note that in this case, the above is actually stamp coupling; it would be better just to send specifically the Coordinate
, and not the whole Tweet object. I included Tweet just to show that “both class in arguments and classes interacted with in the method are dependencies”.)
We illustrate this relationship below.
Notice specifically the direction: the arrow points from the client (ClosestCenterStateFinder
) to the dependency (Tweet
, Coordinate
)
Aggregation/Composition refer to when one class possesses another, specifically as a field (that is, one object aggregates other objects, or is composed of other objects).
For example, in ClosestCenterStateFinder
above, the class possesses multiple instances of State (as a List
, but since List
is a common enough idea for storing a collection of data, we don’t need to diagram List
out in our diagram.) This can be diagrammed as below.
Specifically, the diamond is on the side of the owner, that is the class that has the field containing the other class. Additionally, we label the line with the field name (annotating that this is a List
).
The visible numbers (1
and 1..*
) refer to multiplicity, which we will address soon.
For the difference between aggregation and composition, refer back to the Class Relationships module.
In general, we can think of a bidirectional association as a relationship where both classes meaningfully are aware of one another, often through mutual aggregation. For example, if I have a Student
object, I may want to get the Course
objects that the Student
is enrolled in, but similarly if I have a Course
object, I may want to get the Student
objects in the course.
In this way, it makes sense to model the data with a mutual aggregation. We illustrate that below.
Notice the key difference is a lack of a diamond indicating which side is the “owner”.
All aggregation, composition, and bidirectional association relationships should communicate “multiplicity”. In general, this breaks down into 3 groups:
In general, we express the in the diagram using:
1
- Exactly one1*
- One or more0*
- Zero or morex..y
- between x and y (inclusive). Example, 0..1
means zero or one.For instance, ClosestCenterStateFinder
to State
indicates that ClosestCenterStateFinder
has 1 or more States
.
Meanwhile, in our Student
and Course
example:
This means “Each student is enrolled in zero or more courses, each course has zero or more students”.
Now, you might ask “But if someone is enrolled in zero courses, how are they a student? Or if a course has no students, the course doesn’t exist?” My response would be “when a student is first created, they aren’t enrolled in any courses, and when a course is first created, it has no students. Therefore, both zero cases are valid states.”
Here, it’s important to remember we are talking about data, and the state that data can be in, not real objects. Student
isn’t a student. Student
is data that models information about a student. A Student
isn’t a type of user. A Student
can’t actually enroll in courses. Student
is just data that lets us know information about a human student, including which courses they are enrolled in, which we track with a List<Course>
.
For instance, if we had a class called CourseCatalog
, well, a human student using our software would interact with that class while registering for courses. But that is not the same thing as saying Student
uses CourseCatalog
. Because Student
isn’t a human student using our software.
Always remember, we are talking about how data and procedures are interacting within our code base, not the real world!
Finally, our last relationship is realization/generalization, which refers to inheritance (through implementing interface
s or extended other class
es). This is denoted with a triangular arrowhead. The arrow head points to the class being implemented/extended (i.e., the interface or parent class.)
In this context, the interface
refers to interface
as described in Java code: a set of abstract functions with an intended behavior, but not tied to any one specific implementation. An interface
is not a user interface!
In the above, we see that ClosestCenterStateFinder
realizes the StateFinder
interface. In general, a dotted line is used for realization (implementing interfaces), and a solid line used for generalization (extending a class).
I will note that many developers view UML Diagrams to be a waste of time. In general, my view on the benefits UML Class Diagrams is two-fold:
1) Practicing UML diagrams as a newer developer can help you make mental connections that will pay off when designing software systems later. I liken this to learning LISP. Learning Common-LISP undoubtedly made me better at writing and testing functions. I moved away from “side effect” heavy code when unnecessary, and it’s helped me learn how to decompose software more effectively. Do I use Common-LISP regularly? No, the last time I used it was 2012. But it helped me. 2) I do think diagrams can help act as a communication tool specifically to help people learn a new software system. However, even better than UML Diagrams is effective code documentation.
Does this mean I recommend diagramming everything? No! UML Diagrams take time to make, and, just like any other form of documentation, can become outdated if not readily maintained.
However, I have used UML Class Diagram relationships to “sketch out” a design idea on the board. Additionally, I find UML Diagrams are a useful way to help visually communicate design pattern structure, which will be important when learning common design patterns. But when I say “sketch out”, I mean on a whiteboard to help me solidify an idea before I jump into code. I do not directly translate UML Diagrams to code, for example, and I let myself make design changes while writing code, as I will find ideas that I overlooked in my original design.
So do not take me presenting this unit as a vital industry skill that all companies require programmers to do: it isn’t, and they don’t. However, UML class diagrams are a common enough means of communication that they are worth learning the basics of.