Suppose you are about to develop a system concerned with scheduling
work for, let's say, plumbers.
You begin by doing some analysis of the business itself, so that you
can get clear understanding of the important issues that the prospective
users think about and the system will have to support.
You're told that plumbers mustn't be scheduled to do jobs they don't
have the skills for. Enquiring further, you ascertain that there are different
categories of jobs, and each category requires different skills. Each plumber
may have a different repertoire of skills. (Maybe you enquire about different
graduated levels of skill, but no, for these purposes it's sufficient to
say that a each plumber either has a particular skill or not.)
So at some
stage you draw a picture like this. This establishes clearly the vocabulary
of entities and relationships that the users think in terms of. (We'll
add operations later to make it an object model; but one thing at a time!)
We know now that there is one plumber to a Job; we've made clear the difference
between a Job and a Job Category (both of which the users call 'Job' in
everyday conversation).
Business rules: invariants
Now we can use this model to make further statements. For example, here's
a business rule --- in this case one that takes the form of an invariant,
something that should always be true:
-
Each Job's assignee's skills include all the Job's category's requirements.
Notice how we're sticking carefully to just using the words declared in
the model: that way, there is less ambiguity about what is being said.
The statement is about the relationships that should always obtain between
one set of skills and another.
Rather than using natural language for this invariant, we can use a
formal one --- OCL. The same statement in OCL looks like this:
-
Job :: assignee.skills --> includes
(category.requirements)
The initial "Job ::" says "the remainder
of this clause applies to each instance of the type Job": the terms in
the clause may refer to any attributes or associations declared for that
class (date, assignee, category). The "." operator takes an instance and
yields one of its attributes or associations. Wherever the type diagram
labels an association-role as having cardinality other than 1, the association
denotes a bag (like a set, but can have more than one occurrence of any
one instance). "xx-->includes(yy)" is a comparator between sets, and takes
the value true iff every member of yy can also be found in xx (mathematicians
would use the subset-or-equal operator).
Instance diagrams
or 'snapshots' like this, which illustrate typical situations that do or
don't conform to an invariant, can help to make clear what it is saying.
Here we can see that Job 32's assignee, Fred, has skills that don't include
all of the requirements for the job's category.
Whether the invariant is written in OCL or plain language, the UML type
diagram is providing the declarations of the terms --- just as declarations
in a program define the words you'll use in the program statements. And
just as the program's real meat is usually in the statements rather than
the declarations, so the things you draw in UML are really just a preliminary
to the rules you can define in OCL. OCL provides a very powerful way of
stating business rules unambiguously.
Notice that the same rule could have been written from a different point
of view. It doesn't matter too much if you choose to write it this way
instead:
-
Plumber:: skills.jobTypes -->includes(schedule.category)
Where there is no explicit role name at one end of an association, the
name of the type is assumed (with lower case and perhaps a plural ending):
e.g. "jobTypes". The "." operator can take a bag and apply the same attribute
or association to each member, thereby yielding another bag. So we're saying
here:
-
For every Plumber, all the types of job for which they have the skills
includes all the types of job they are scheduled to do