Class diagrams, when used in conjunction with sequence diagrams, provide an extremely effective communication mechanism. You can use a class diagram to illustrate the relationships between the classes, and the sequence diagram lets you show the messages sent among the instances of these classes and the order in which they are sent. When an object sends a message to another object, it implies that the two classes have a relationship that must be shown on a class diagram.
Many of the elements that compose the Unified Modeling Language (UML) have a precise mapping to the Java programming language. So, I can demonstrate how class and sequence diagrams complement each other by looking at an example of the UML that simulates the standard Java event-handling mechanism. When developing Java applications, it's quite common to use a pattern similar to this example to handle events within your application.
In Figure A, the EventExample object sends a message to the TimeEventSource object. This message results in the addTimeChangeListener() method being triggered. The ordering of the messages sent between objects is always read top to bottom and typically is read left to right. Notice the notes used in Figure A to enhance understanding.
Let's walk through the sequence diagram in further detail. The numbers that follow correspond to the numbers in the figure:
- Some EventExample, which can be any object in your system, begins your event simulation by creating a TimeChangeListener object. Obviously, you can't have an instance of an interface. However, it's important to communicate that the TimeEventSource isn't coupled to the implementation of the TimeChangeListener but to the listener itself.
- The EventExample object now registers the TimeChangeListener with the TimeEventSource object.
- EventExample calls start on the TimeEventSource, which begins sending events to the listeners that have been registered with it.
- The TimeEventSource creates a TimeChangeEvent object, which encapsulates information regarding this event.
- The TimeEventSource loops through each of its listeners, calling the timeChange method on each.
- Optionally, the TimeChangeListener can obtain a reference to the object that caused the event notification. This reference is returned as a generic reference to java.lang.Object.
- TimeChangeListener calls its processEvent() method to handle processing the event.
You'll notice that I have attached a note to the TimeChangeListener interface specifying that I will actually create a class that implements this interface. This note allows for a great deal of flexibility in the diagram, because the entire message sequence holds true regardless of what class I use in place of TimeChangeListener. I also may want to use a note to specify that when the TimeEventSource object notifies its listeners of a time change, it may notify multiple listeners, resulting in a loop.
The point is that developers who need to interpret this diagram and use it to construct code must have the information provided in the notes to do so effectively. Notes are a great aid in helping you understand more fully the details associated with this sequence of events. Notes should appear on most of the diagrams. You typically have many sequence diagrams for a single class diagram because any society of classes will interact in many different ways. The intent of a sequence diagram is to model one way in which the society interacts.
The class diagram in Figure B is a structural representation of the Java event simulation. Note each of the relationships that appears on this diagram. First, the EventExample class has relationships to TimeEventSource and TimePrinter, which corresponds to the messages an EventExample object must send to instances of these classes.
The sequence diagram in Figure A didn't include a TimePrinter object. In fact, it did contain a TimePrinter, but it was in the form of the TimeChangeListener interface. As you can see in Figure B, the TimePrinter implements the TimeChangeListener interface. Also, notice the structural inheritance relationships depicted on this diagram that weren't apparent on the sequence diagram.
When interpreting the diagrams, it is often easiest to place the class diagram and sequence diagram side by side. Begin reading the sequence diagram, and as the messages are sent between the objects, trace these messages back to the relationships on the class diagram. Doing so should be helpful in understanding why the relationships on the class diagram exist. After reading the sequence diagrams, if you find a relationship on a class diagram that can't be mapped to a method call, you should question the validity of the relationship.
Package diagrams are a form of a class diagram. The difference is that a package diagram shows the relationships between the individual packages. You can think of a package diagram as a higher-level view into your system. This becomes important when understanding the system's architecture. The dependency relationships illustrated in Figure C indicates the direction of the structural relationships among the classes within the various packages.
As you can see, the eventhandling package is dependent on the util package, which implies that classes within eventhandling can import classes within util but not vice versa. This is an important distinction because your package dependencies must be consistent with the relationships expressed on the corresponding class diagrams.
Package diagrams, when combined with class diagrams, are an effective mechanism for communicating a system's architecture. A diagram such as the one in Figure C provides a high-level glimpse into a system's overall structure. This enables developers to make assumptions regarding the relationships between individual classes, which becomes especially helpful as new developers join the project and need to be quickly brought up to speed—or when developers need to maintain a system they may have previously worked on but haven't interacted with in some time.
Either way, this form of architectural modeling is beneficial. You can download the code for this example in Appendix C at JOUP On-line.
As you've seen, many of the elements of the UML have a precise mapping to the Java programming language, which is consistent with the claims that the UML is a precise and unambiguous communication mechanism. As developers, we must interpret each of these elements in a fashion that is faithful to this claim. Different interpretations of these elements can result in miscommunication, which is the very challenge the UML attempts to resolve.
Although the UML is precise, you must make sure that you don't derive more meaning than the diagrams actually convey. The UML is not a visual programming language but a modeling language. As such, the intent is not to model at the same level of detail at which code exists. Diagrams typically do not state how to implement something, communicating instead only that you must accommodate a particular need.