Appendix A. Object Constraint Language

Chapter 5 introduced writing constraints in your class diagrams using OCL. You don’t have to use OCL to express constraints—you can use your favorite programming language syntax or even natural language. This appendix discusses the advantages of OCL and provides more details about how to more use OCL.

Recall from Chapter 5 that a constraint is written in curly braces after the element it constrains or displayed in an attached note. Figure A-1 shows different ways of specifying that the attribute rating has to be non-negative.

Different ways of attaching and expressing a constraint
Figure A-1. Different ways of attaching and expressing a constraint

Figure A-1 shows that the words expressing a constraint can vary. Constraints can be written in natural language, such as:

rating is greater than or equal to zero

Constraints can also look like a programming language expression, such as:

rating >= 0

Because natural language can be ambiguous (and long-winded!), many modelers use syntax similar to their preferred programming language: notice that rating >= 0 looks like a Java or C expression.

Constraints can get more complicated; for example, they can specify that a value isn’t null. This means you have a lot of options for expressing constraints, so how do you decide which notion to use? Such an expression may look different in different programming languages. If constraints are expressed in a standard and predictable way, not only can you easily understand the constraint, but also automated tools can understand the constraint. This allows automatic checking of constraints in your diagrams and in the code generated from the diagrams.

Because of this, the Object Management Group (OMG, the group behind UML) was convinced that a single formal constraint language was needed, but the language had specific requirements. The language had to allow values to be checked but not changed—in other words, it had to be an expression language. The language had to be general enough that you could use it to express constraints in your UML diagrams regardless of your target implementation language. And finally, the language had to be simple enough that people would actually use it, which is not true of many formal languages.

OCL, developed at IBM for business modeling, had all of these features, and so it was a perfect match. So, OCL was chosen to work alongside UML to provide a formal yet easy-to-understand language for specifying constraints.

You don’t have to use OCL. In general, modelers decide to use OCL depending on a combination of factors, including how extensively they model and how important they consider design by contract (discussed later). If these factors apply to you, OCL is worth considering because automated constraint checking allows greater integrity of your model.

In UML diagrams, OCL is primarily used to write constraints in class diagrams and guard conditions in state and activity diagrams.

Building OCL Expressions

Figure A-2 shows a class diagram with a few OCL expressions, including:

Simple number comparison

baseCost >= 0.0

More complicated number comparison

totalCost = baseCost * (1+getTaxRate( ))

String comparison

status <> 'Unpaid'

Example OCL constraints of varying complexity
Figure A-2. Example OCL constraints of varying complexity

Tip

Unlike many languages, such as Java, in OCL the = operator is used to check whether two items are equal, not to assign a value.

OCL expressions consist of model elements, constants, and operators. Model elements include class attributes, operations, and members though association. The OCL expressions in Figure A-2 use the model elements baseCost, totalCost, and getTaxRate( ). (Later sections contain OCL expressions with members through association.)

Constants are unchanging values of one of the predefined OCL types. In Figure A-2, constants include 0.0 of type Real and 'Unpaid' of type String. Operators combine model elements and constants to form an expression. In Figure A-2, operators include <>, +, and =.

The following sections discuss the basics of OCL types and operators and then show how to combine these into expressions you can use in your UML models.

Types

OCL has four built-in types: Boolean, Integer, Real, and String. Table A-1 shows examples of these four types. These examples are typical constants you could encounter in OCL expressions.

Table A-1. Built-in OCL types

Type

Examples

Boolean

true; false

Integer

1; 6,664; -200

Real

2.7181828; 10.5

String

“Hello, World.”

Operators

OCL has the basic arithmetic, logic, and comparison operators. OCL also has more advanced functions such as returning the maximum of two values and concatenating Strings. OCL is a typed language, so the operator has to make sense for its values. For example, you can’t take the sum of an Integer and a Boolean. Table A-2 shows commonly used operators in OCL expressions.

Table A-2. Commonly used operators in OCL expressions

Group

Operators

Used with types

Example OCL expression

Arithmetic

+, −, ∗, /

Integer, Real

baseCost + tax

Additional Arithmetic

abs(), max(), min( )

Integer, Real

score1.max(score2)

 

Comparison

<, <=, >, >=

Integer, Real

rate > .75

Equality

=, <>

All

age = 65

title <> ‘CEO’

Boolean

and, or, xor, not

Boolean

isMale and (age >= 65)

String

concat(), size(), substring( ), toInteger(), toReal( )

String

title.substring(1,3)

Operators in the groups Comparison, Equality, and Boolean all return results of type Boolean. For example, age = 65 evaluates to true or false. The other operators in Table A-2 return the same type with which they’re used. For example, if baseCost and tax are Real, then baseCost + tax will also be Real.

Figure A-2 shows that getTaxRate( ) returns a double (this model was written with Java types), but the table in Table A-2 mentions that the operator + is defined on Reals and Integers. That’s perfectly fine; when building an OCL expression, you can match your types to the closest OCL type.

Tip

OCL can also express operations on collections, such as unions of sets. For a more complete list of OCL expressions, see UML 2.0 in a Nutshell (O’Reilly).

Pulling It Together

So far you’ve seen the building blocks of OCL expressions. Now let’s combine them to build a sample OCL expression.

totalCost = baseCost * (1+getTaxRate(  ))

This OCL expression is taken from Figure A-2. It contains the following building blocks of an OCL expression:

Model elements

totalCost, baseCost, and getTaxRate( )

Constant

1

Operators

=, *, and +

The above expression actually consists of several OCL expressions, which are in turn combined by operators. For example, 1+getTaxRate( ) evaluates to a Real, which is then multiplied with baseCost. That resulting value is checked for equality with totalCost using the = operator. You can combine model elements, constants, and expressions according to their type, but the combined expression must be type Boolean. This is because we’re focusing on using OCL to express constraints and guards, which must evaluate to true or false.

Another commonly used constraint is to specify that an object isn’t null. To specify that an object isn’t null, you have to use the OCL’s notation for sets and operations on sets. Figure A-3 shows how to check that Author’s member through association fee isn’t null using the expression:

self.fee->notEmpty(  )
Constraining that a member isn’t null
Figure A-3. Constraining that a member isn’t null

Notice the reference to self in the OCL expression in Figure A-3. Because it is attached to Author, self refers to objects of type Author. The self keyword is commonly used when you set a context in an OCL expression, as shown in the following section.

Context

Figure A-2 defined OCL expressions on the elements they constrain, while Figure A-3 defined an OCL expression on the containing class. You can write an OCL expression at different areas in your diagram. How you write the OCL expression depends on the context, or where you are in the diagram.

Figure A-4 shows how to check that baseCost of AccountFee is greater than or equal to 0 at different reference points in the diagram. The first diagram shows this constraint in the context of baseCost, the second shows this constraint at AccountFee, and the third shows this constraint at Author.

The way you write a constraint depends on your reference point in the diagram
Figure A-4. The way you write a constraint depends on your reference point in the diagram

If your reference point is baseCost, e.g., by writing a constraint in curly braces after baseCost, then you write:

baseCost >= 0.0

If you’re referring to the AccountFee class, e.g., by attaching a note to the AccountFee class, then you write:

self.baseCost >= 0.0

Finally, if you’re referring to the Author class, e.g., by attaching a note to the Author class, then you write:

self.fee.baseCost >= 0.0

You can also write OCL constraints that aren’t physically attached to model elements. For example, your UML tool may provide a text editor for entering constraints. If you do this, write the context explicitly. If the context is the AccountFee class, then you write:

Context AccountFee
inv: self.baseCost >= 0.0

The inv keyword indicates that the constraint is an invariant, or a condition that must always be true. When specifying the context, you also specify the type of constraint it is. Constraint types are discussed in “Types of Constraints,” next.

Types of Constraints

There are three types of constraints:

Invariants

An invariant is a constraint that must always be true—otherwise the system is in an invalid state. Invariants are defined on class attributes. For example, in Figure A-4, the baseCost attribute of AccountFee must always be greater than or equal to zero.

Preconditions

A precondition is a constraint that is defined on a method and is checked before the method executes. Preconditions are frequently used to validate input parameters to a method.

Postconditions

A postcondition is also defined on a method and is checked after the method executes. Postconditions are frequently used to describe how values were changed by a method.

Previous examples in this chapter focused on invariants, but all three constraint types can be expressed in your UML diagrams or related documentation. The following examples will show how to provide preconditions and postconditions for the method incrementRating. The reference class diagram is shown in Figure A-5.

We’ll provide preconditions and postconditions for the incrementRating method
Figure A-5. We’ll provide preconditions and postconditions for the incrementRating method

Suppose incrementRating will increment the value rating by the value amount. We want to first specify a precondition that amount is less than a maximum legal amount, say 100, and a postcondition ensuring that rating has been incremented by amount. To write these preconditions and postconditions, you first specify the context and then the constraints.

context BlogEntry::incrementRating(amount: int) : void
pre: amount <= 100
post: rating = rating@pre + amount

Notice the @pre directive: rating@pre is the value of rating before the method executes. You can use the @pre notation on methods too:

context BlogEntry::incrementRating(amount: int) : void
pre: amount <= 100
post: getRating(  ) = getRating@pre(  ) + amount

Invariants, preconditions, and postconditions are part of an approach known as “Design by Contract,” developed by Bertrand Meyer. “Design by Contract” attempts to make more reliable code by establishing a contract between a class and its clients. A class’s contract tells its clients that if they call its methods with valid values, then they will receive a valid value in response. A contract also establishes invariants on a class, meaning that the class’s attributes will never violate certain constraints.

If you’re wondering why you haven’t encountered invariants, preconditions, and postconditions in your code, note that support for Design by Contract differs per programming language. “Design by Contract” is built into the Eiffel programming language, which was also developed by Bertrand Meyer. Eiffel has keywords for invariants, preconditions, and postconditions, and an exception is thrown if any of these constraints are violated. With other languages, you have to either implement constraint handing yourself or use a package such as iContract for Java. iContract is a preprocessor with doc-style tags to specify invariants, preconditions, and postconditions. iContract also throws an exception if a constraint is violated.

OCL Automation

The real power of OCL comes from tools that can use the OCL constraints from your UML model to perform constraint checking for you. While at the moment there is wide variation in tool maturity and level of integration, the ultimate goal is to enhance integration of your UML model with the runtime behavior of your system. This has the benefit of allowing you to catch errors early and saving on debugging time.

Some UML tools focus on placing the OCL constraints from your diagrams into generated code so that the constraints can be checked at runtime (although at the moment, these may only be proposed or partial implementations). Example approaches include generating assert statements directly in your code to allow constraint checking, or embellishing your code with Java annotations or doc-style tags containing OCL constraints, which can then be used by standard OCL tools that can check constraints at runtime. For example, the open source UML tool ArgoUML inserts OCL constraints into generated Java code as doc-style tags. With doc or annotations in your code, you can take advantage of OCL tools (such as ocl4java or the Dresden OCL Toolkit) that perform code enhancement to provide you runtime feedback about constraint violations in your executed code.

Stay on the lookout for developments in this area; as MDA and Executable UML (introduced in Chapter 1) become increasingly central to UML, you can expect even more of these capabilities to be integrated with modeling tools.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.23.130