Earlier, I said that I preferred a separate class to represent individual records in the set of items from the database; but that class doesn’t appear in Figure 2-44 or Figure 2-46. Why is that?
Well, I could have added a separate swimlane to represent that class ( COwnerRecord); and then I could have shown transitions from COwnerSet to that swimlane to indicate when a record object was created. That would be entirely legal UML; but would it communicate? I don’t think so: in the context of this diagram, that swimlane would have no other responsibilities. So it would take up space and add detail without really adding information.
Instead, I prefer to use a more advanced part of the Activity Diagram notation: object flow states. These are icons you add to the Activity Diagram, not to represent activities the system will perform, but simply to represent objects that are manipulated by the activities. An object flow state (or more simply, an object) appears as a rectangle with the name and the class of the object, separated by a colon, both underlined. (If you haven’t selected both a name and a class yet, just list the one you have selected.) You can show an activity creating or modifying an object by a dashed arrow from the activity to the object; and you can show an activity reading information from an object by a dashed arrow from the object to the activity.
For example, we could add the COwnerRecord to the diagram in Figure 2-46 as shown in Figure 2-47.
There are three situations in which I find object flow states to be particularly useful. One is when I need to demonstrate creating an object that’s a return value, as in Figure 2-47. I find that it helps to see where the return object comes from.
The second is when I want to show some sort of cumulative results from within a loop, which are then used by an activity outside the loop. The object flow state can represent the cumulative results, as shown in Figure 2-48.
The third situation in which I find object flow states to be particularly useful is when I want to show an object through which two threads communicate and share information. This object can then hold any synchronization mechanism used to prevent contention between the two threads. If the two threads both write to the object (such as, perhaps, a logger object), then both threads would have outgoing transitions to the object, such as those shown in Figure 2-49.
But if one thread is using the object to signal or control the other thread, then one transition should go in, and one should go out, as shown in Figure 2-50.
A common practice when working with object flow states in this way is described by the Three Amigos:
The control flow (solid) may be omitted when the object flow (dashed)
arrows supply a redundant constraint. 15
In other words, if an object flow state and a transition connect the same two activities, as shown in Figure 2-51, then you may omit the transition, as shown in Figure 2-52.
This is legal UML, but I strongly discourage it. Now usually, I’m very flexible in the use of UML: you’ve seen that already in this chapter. So it surprises my students how strongly I object to this usage. But in my experience, this trains readers to think that dashed arrows always indicate control flow, not just data flow; and once they’re trained in that wrong habit, other diagrams can really confuse them. Look back at Figure 2-48: suddenly, Cumulative Result looks like some sort of “escape hatch,” allowing a premature exit from the loop. Even worse, look back at Figure 2-50: suddenly, Thread Signal looks like some sort of mechanism for breaking the wall between thread execution contexts, allowing the CPU to “jump the track” from one context to the other. Neither of those misperceptions is a correct reading of the diagram according to the rules of UML; but both misperceptions are encouraged when people are used to seeing the redundant transitions omitted. Thus, this habit leads to miscommunication, which is never our goal with UML.
Don’t go overboard with object flow states, because they may add excess detail to the diagram. Consider drawing the diagram with and without the objects, and then deciding which communicates better.Creating Class Diagrams
Recall that our classes correspond to the swimlanes from the component Activity Diagrams. So for instance, look back to our latest version of the Activity Diagram for the Get or Add Owner use case (Figure 2-47). You can see our initial Class Diagram would look like the one in Figure 2-53.
Then from the Activity Diagrams, you need to identify associations and dependencies. For each class, examine its corresponding swimlanes in the Activity Diagrams. Look at the other swimlanes that have transitions into those swimlanes. Those transitions define either associations or dependencies. As discussed earlier, if the relation is persistent, draw it as an association (a solid line); but if the relation is transitory (lasting no longer than a single operation, as a rule), draw it as a dependence (a dashed arrow). You can also model return types via dependence. A good convention is to draw a class using an interface as a dependence, because an association (a solid line connecting the class to the interface) would look just like a realization (a solid line connecting the class to the interface).
Thinking about this example. It will make sense for COwnerInfo to always have a COwnerSet with which it is working, so we’ll add that as an association. COwnerSet uses the SQL interface, so that is a dependence; and because COwnerSet creates COwnerRecord objects as it needs them, we also model that as a dependence. Finally, because the Owner Info interface—and thus the COwnerInfo class—return a COwnerRecord, those should also be dependent on COwnerRecord. Thus, we end up with Figure 2-54.
In this case, however, I feel that the dependence of Owner Info on COwnerRecord just clutters the diagram with not a lot of benefit. I prefer the diagram in Figure 2-55.
Next we’ll look at the swimlane for each class, and convert its activities into operations of the class. Adding these to the class icon, we get the diagram shown in Figure 2-56.
Note I didn’t add Look Up Owner nor Add Owner Record to COwnerInfo. Those represented logical steps within Get or Add Owner, not separate operations. I could even have hidden these within a Get or Add Owner subactivity state in Figure 2-47; but I felt that would complicate the picture, not improve it. Later, I might decide that neither Look Up Owner nor Add Owner Record are useful operations to have to simplify the code; but for now, I haven’t committed to that choice.
Finally, let’s add our attributes for these classes. Earlier examples tell us that a COwnerRecord needs First Name and Last Name attributes. We could depict these as shown in Figure 2-57.
Step 5(a): Repeat AgainNow narrow the scope of your process to individual classes. Repeat Steps 1 through 4 as appropriate for the current scope.
Once you have your Class Diagrams, it’s possible to go through Step 5 again, this time narrowing your scope to look at a single class. By zooming in to the scope of the class, you can apply similar analysis and design processes to elaborate the structure of the class.
So in this step, you’ll perform the following substeps:
Now it may be that you find it easy enough to jump straight to code, skipping this step. Great! Do what works for you. But the same strategies that helped you to simplify complexity in requirements and in components may be useful in studying and designing complex classes.
From one of the Class Diagrams you’ve developed in the last exercise, pick a class to design. Then walk through the procedures described next to elaborate the design of that class.
blog comments powered by Disqus