3.3 Using use cases to discover types
So far we have seen how to describe and draw pictures of objects but little has been said about how to go about discovering them. The most popular technique starts with a set of use cases that describe how a system interacts with its environment: typical interactions involving its users.
Figure 13 Use cases and the types they refer to.
Catalysis introduced the idea of actions, which generalize both use cases and operations. An action is any goal-oriented collaboration, activity, task, operation or job connecting agents in a business, system or project. When specifying systems, the most interesting actions are use cases. A use case is a goal-oriented collaboration between a system and an actor; where an actor is a user adopting a rôle. Such a 'user' can be a device or component as well as a person. An agent is anybody or anything adopting a rôle; as opposed to a user doing so. We regard agents and actors as objects, because they exhibit state and behaviour and have identity. If we know the initiator of a collaboration then we can think of a usage dependency as representative of the action. Consider the simple act of buying something illustrated in Figure 13. The ellipse represents an interaction between two agent instances which results in something changing ownership between them in return for money changing hands in the other direction. This is represented by an informal post-condition on the action written in the note illustrated. This post-condition only makes sense in terms of the objects it refers to: its parameters. In this case we have identified Thing as a type that must be included in our vocabulary and shown it on our initial drawing. Additionally, we have picked out some of the nouns in the post-condition. This is a very useful technique for inferring the type model from the use cases. We show candidate types in bold, with attributes underlined and potential associations italicized.
We can see from this example that an action always results in a change of state that can be expressed by a post-condition. Catalysis introduced the idea of illustrating this change using instance snapshots. We draw instances of the candidate types and their associations at a particular point in time, before the occurrence of the action. Then the result of applying the use case is expressed by showing which associations are deleted and added to the diagram. In Figure 14 the association between sock17 and the vendor is crossed out from the 'before' diagram (indicating deletion) and added to the 'after' diagram. Associations in after snapshots are shown by thick grey lines. Note also that the values of the pocket and till attributes are crossed out and replaced with new values.
Figure 14 An instance snapshot.
Any snapshot must conform to the type diagram associated with the use case it illustrates. Snapshots are useful as an aid to clear thinking during analysis and design. They represent instances of use cases. Although simple ones may be included in formal documentation, We usually recommend against this. After drawing them, the type model can usually be updated. Note in particular that neither the action post-condition nor the snapshots say anything about the sequence of events; they only describe the outcome, regardless of the precise sequence of events leading to it. This is important when we come to the design of reusable components because two components (or two business units) may implement the use case quite differently. The interface to a reusable component should only specify the outcome and the vocabulary, not the sequence of implementation operations that leads to that outcome.
Every action, and a fortiori every use case, has a name, a goal, a list of participants (usually two in business analysis) and a list of parameters: the objects involved (actively or passively) that are different from one occurrence of the use case to another. The goal represents its specification: the result(s) it achieves, and/or a condition that it maintains. There may also be a pre-condition: a statement defining under what conditions the use case makes sense. Thus, when documenting an action the following form is recommended:
type name (parameters)
For example, referring to Figure 13, we might write:
use case buy_thing (Purchaser, Vendor, Thing)
pre: vendor owns the thing and purchaser can afford it
post: vendor.possessions reduced by thing
and purchaser.possessions increased by thing
and vendor.till += thing.price
and purchaser.pocket -= thing.price
We could even express this with full mathematical formality using UML's Object Constraint Language (OCL) as follows:
use case buy_thing (Purchaser, Vendor, Thing)
pre: vendor.possessions -> includes thing
and purchaser.pocket >= thing.price
post: vendor.possessions = vendor.possessions - thing
and purchaser.possessions = purchaser.possessions + thing
and vendor.till = vendor.till@pre + thing.price
and purchaser.pocket = purchaser.pocket@pre - thing.price
In the above, the expression @pre refers to the value held by the attribute in the before state and ->includes indicates set membership. The + sign is an abbreviation for ->union (ditto the - sign mutatis mutandis). Another convention used here is inherited from Fusion (Coleman et al., 1994): if all the parameters have different types, their names are the same as the type names, but starting with lower case. The same convention is used for unlabelled associations. Using upper case means one is saying something about the type. Notice also that there is no attribute of Thing named price. The only purpose of the attributes is to provide a vocabulary for the action specifications.
OCL specifications are not to everyone's taste and do not need to be used. But it is reassuring for those of us who work on high-integrity or safety-critical systems that such formality is possible. We will not use OCL very much in this text.
Figure 15 Composing and decomposing use cases.
The static type model provides a vocabulary for expressing use cases. The use case model defines behaviour using the vocabulary from the static model. Snapshots illustrate use case occurrences and help clarify thinking about the type model.
Use cases (and other actions) are objects; so that they can be associated, classified and aggregated just like other objects. In our example of a sale, we can see that there can be various kinds of sale that specialize the concept. Note that the post-conditions are all strengthened in the sub-actions. Pre-conditions, if any, must be weakened. We will see later that this is a general rule for inherited pre- and post-conditions. It is also possible to abstract and refine use cases using aggregation as illustrated in Figure 15 and using inheritance as Figure 16 shows. Figure 16 gives an example specialization of sale; note that a specialized use case achieves all the goals of its parent(s).
Figure 16 Generalizing and specializing use cases.
Figure 16 contains a deliberate faux pas, intended to illustrate a common error. It is quite wrong for the same inheritance arrow to be used to connect the completely incomparable notions of a petrol sale and a credit sale. We should show the two orthogonal hierarchies separately; each inheritance arrow corresponds to a discriminator such as 'fuel type' or 'payment method'. Always ask yourself 'what is the discriminator?' when drawing a specialization diagram of any sort.
The left-to-right ordering in aggregation diagrams like Figure 15 does not imply any temporal sequence; the use cases could happen in any sequence, be repeated or be concurrent. However, we can add such information by creating associations between actions using «uses» dependencies. UML specifies two particular dependencies designated «include» (originally «uses») and «extend» (originally «extends»). However, since these are poorly and inconsistently defined and since «extend» violates encapsulation (Graham et al., 1997a) we will not use them in this tutorial. Our experience is that their use sows confusion and, besides, the standard object semantics that we have laid down for static models enables us to do everything without introducing additional terminology.
The goals of use cases provide the basis for building a test harness very early on in analysis, because they relate directly to the purpose and function of a system. This also facilitates eXtreme Programming (Beck, 2000).
Notice how the actors in the use case model correspond to types in the type model in our preliminary attempt. Also, if we regard the type model as providing the vocabulary for defining the use cases, we can see that this provides a link between two different kinds of UML diagram. It was a major innovation of Catalysis to show how the UML diagram types were related.
Figure 17 Sequence diagrams and refinement.
Just as snapshots help us visualize the type model, scenarios and sequence diagrams help with the visualization of use cases. Scenarios are stories about how the business works. They can be written out in prose and this is often a useful thing to do. They describe typical sequences that occur when a use case is realized and can include variations on the basic sequence. They can be illustrated with UML sequence or collaboration diagrams of the kind shown in Figure 18 or, better, with the Catalysis-style sequence chart of Figure 17. The distinctive feature of the latter is the action-instances, each of which may touch more than two objects, and need not be directed.
UML sequence diagrams only describe OO programming messages, each with a receiver and sender. In both cases the vertical lines represent the instances of the types indicated above them and the horizontal lines represent actions that involve the instances that they connect. Both dimensions can be expanded to refine the analysis and provide more detailed steps and a finer scale; each event can be expanded to more detailed sequences and each object to more detailed objects. In the figure we see that one can zoom in on the details of the buy(drum) use case to show details of the vendor's business process sequence. We can also zoom in on the vendor object to reveal details of his business organization as well. The trick is to choose the level and scale that you are working at: essential 'my pay was credited to my account yesterday, so I went to get some cash today'; detail 'to get money from your account, you insert your card into a cash machine, enter PIN, ...'; grandiose 'A good way of making money is to run a bank. Provide accounts with higher rates of interest in exchange for less accessibility. They should be able to get cash from the most accessible accounts at any time of day or night.'
Figure 18 Sequence and collaboration diagrams.
Sequence charts for scenarios help to identify the different participants in the action and their interactions and to show the sequence of events. This helps with the conceptualization of the rôle of time and with understanding dependencies between instances. Sequence charts assist the designer in allocating instances among distributed system components and are often useful during testing. They also help with exploring interference between multiple use cases.
Notice that zooming is based on aggregation in the examples given above. Inheritance can also lead to refinements; so we could zoom from buy(drum) to buy(bodhran), which would involve the purchaser selecting a tipper and testing the goatskin for usefully melodious imperfections.
Refinement is one of the basic principles of Catalysis and aids traceability by linking specification level use cases and types to implementation interfaces and their vocabularies. Typically, the different levels of refinement will be contained in different implementation packages.
Sequence diagrams emphasize visualizing the allocation of responsibility: it is easy to shift tasks sideways. They are equivalent to collaboration diagrams which emphasize static relationships rather than temporal ones. Collaboration diagrams help you see what associations are being affected, and the dependencies between the objects (which, of course, you want to minimize). Sequence diagrams are good at illustrating the assignment of responsibilities but cannot express control flow variation well. Collaboration diagrams are better at showing associations and control flow. This helps us to think about better decoupling. Figure 18 shows a sequence diagram and its associated collaboration form. Arrows or lines joining the instances in the latter represent associations. Messages are indicated by the unattached arrows and their numbering indicates their calling sequence. Detailed as they look, collaboration diagrams still abstract from the precise details of implementation. In this case we see that the association between the manager and the work tray represents a poor design. S/he would be better to access these data through a secretarial service, which can be provided either by Tony or by an access function to a database. A change in the way the job-applications are stored would result in a change to both Secretary and Manager if they both access the Worktray. The moral is that we can reduce dependencies by assigning responsibilities properly; the collaborations help us understand how to assign responsibilities. Figure 18 also shows that collaboration diagrams can be used as snapshots.