3.3 Completing Class Diagrams


The model depicted in a class diagram can be augmented with extra information in numerous places. Most of the examples given in this section augment the class diagram for the R&L system shown in Figure 2-1 of Chapter 2.

3.3.1 Derivation Rules

Models often define derived attributes and associations. A derived element does not stand alone. The value of a derived element must always be determined from other (base) values in the model. Omitting the way to derive the element value results in an incomplete model. Using OCL, the derivation can be expressed in a derivation rule. In the following example, the value of a derived element usedServices is defined to be all services that have generated transactions on the account:

  context  LoyaltyAccount::usedServices : Set(Services)  derive  : transactions.service->asSet() 

Note that the question whether usedServices is an attribute or an association end should be answered from the UML diagram(s). In this case, it was not shown in the diagram in Chapter 2, so we cannot tell. Often, derived attributes are not shown in the diagrams at all, but are defined by an OCL attribute definition (see Section 3.9.1).

3.3.2 Initial Values

In the model information, the initial value of an attribute or association role can be specified by an OCL expression. In the following examples, the initial value for the attribute points is , and for the association end transactions, it is an empty set:

  context  LoyaltyAccount::points : Integer  init  : 0  context  LoyaltyAccount::transactions : Set(Transaction)  init  : Set{} 

Note the difference between an initial value and a derivation rule. A derivation rule states an invariant: The derived element should always have the same value that the rule expresses. An initial value, however, must hold only at the moment when the contextual instance is created. After that moment, the attribute may have a different value at any point in time.

3.3.3 Body of Query Operations

The class diagram can introduce a number of query operations. Query operations are operations that have no side effects, i.e., do not change the state of any instance in the system. Execution of a query operation results in a value or set of values, without any alterations in the state of the system. Query operations can be introduced in the class diagram, but can only be fully defined by specifying the result of the operation. Using OCL, the result can be given in a single expression, called a body expression . In fact, OCL is a full query language, comparable to SQL, as shown in [Akehurs01]. The use of body expressions is an illustration thereof.

The next example states that the operation getCustomerName will always result in the name of the card owner associated with the loyalty account:

  context  LoyaltyAccount::getCustomerName() : String  body  : Membership.card.owner.name 

3.3.4 Invariants

Another way to augment a class diagram is by stating an invariant. The concept invariant is defined as follows :

An invariant is a boolean expression that states a condition that must always be met by all instances of the type for which it is defined. [1]

[1] To be more precise, an invariant must be true in all consistent states of the system. While the system is, for instance, executing an operation, it is not in a consistent state, and the invariant need not be true. Of course, when the execution has finished, the invariant must again be true.

An invariant is described using an boolean expression that evaluates to true if the invariant is met. The invariant must be true upon completion of the constructor and completion of every public operation, but not necessarily during the execution of operations. Incorporating an invariant into a model means that any system made according to the model is faulty when the invariant is broken. How to react when an invariant is broken is explained in Section 4.6.2.

In the following example, all cards that generate transactions on the loyalty account must have the same owner:

  context  LoyaltyAccount  inv  oneOwner: transactions.card.owner->asSet()->size() = 1 

An invariant may be named, which can be useful for reference from an accompanying text. The preceding invariant is named oneOwner .

3.3.5 Preconditions and Postconditions

Preconditions and postconditions to operations are yet another way to complete the model depicted in a class diagram. Because pre- and postconditions do not specify how the body of an operation should be implemented, they are an effective way to precisely define the interfaces in the system. The concepts are defined as follows:

A precondition is a boolean expression that must be true at the moment that the operation starts its execution.

A postcondition is a boolean expression that must be true at the moment that the operation ends its execution.

Note that in contrast to invariants, which must always be true, pre- and postconditions need be true only at a certain point in time: before and after execution of an operation, respectively.

We can use OCL expressions to specify either the pre- and postconditions of operations on all UML types, or the pre- and postconditions of use cases. The first option is explained in this section, the second option is explained in Section 3.8.1. In the following example, a customer that is already known in the system, perhaps because it is already participating in another loyalty program, is enrolled in a loyalty program under the condition that its name attribute is not empty. The postcondition of the enroll operation ensures that the new customer is included in the list of customers belonging to the loyalty program:

  context  LoyaltyProgram::enroll(c : Customer)  pre  : c.name <> ''  post  : participants = participants@pre->including( c ) 

Note that a even a simple postcondition like myVar = 10 does not specify any statement in the body of the operation. The variable myVar may become equal to 10 in numerous ways. Here are some example implementations in Java:

 -- use another variable otherVar = 10; -- include other statements here myVar = otherVar; -- use a calculation myVar = 100/10; -- use another object myVar = someElement;  -- where the value of someElement is 10 

The principle behind the use of pre- and postconditions is often referred to as the design by contract principle. Design by contract can be used within the context of any object-oriented development method. The following paragraphs describe the principle and its advantages.

Design by Contract

The definition of contract in the design by contract principle is derived from the legal notion of a contract: a univocal, lawful agreement between two parties in which both parties accept obligations, and on which both parties can ground their rights. In object-oriented terms, a contract is a means to establish the responsibilities of an object clearly and unambiguously. An object is responsible for executing services (the obligations) if and only if certain stipulations (the rights) are fulfilled. A contract is an exact specification of the interface of an object. All objects that are willing to use the services offered are called clients or consumers . The object that is offering the services is called the supplier .

Although the notion of a contract is derived from law practice, it is not completely analogous when employed in object technology. A contract is offered by a supplier independently of the presence of any client. However, when a client uses the services offered in the contract, the client is bound by the conditions in the contract.

A contract describes the services that are provided by an object. For each service, it specifically describes two things:

  • The conditions under which the service will be provided

  • A specification of the result of the service that is provided, given that the conditions are fulfilled

An example of a contract can be found at most mailing boxes in the Netherlands:

A letter posted before 18:00 will be delivered on the next working day to any address in the Netherlands.

A contract for an express service is another example:

For the price of two euros, a letter with a maximum weight of 80 grams will be delivered anywhere in the Netherlands within 4 hours after pickup.

Table 3-1 shows the rights and obligations of both parties in the express delivery service example. Note that the rights of one party can be directly mapped to the obligations of the other party.

A contract can become much more complicated ”for example, when it concerns the purchase of a house. The important point is that the rights and obligations in a contract are unambiguous. In software terms, we call this a formal specification . Both parties benefit from a clear contract:

  • The supplier knows the exact conditions under which its services can be used. If the client does not live up to its obligations, the supplier is not responsible for the consequences. This means that the supplier can assume that the conditions are always met.

  • The client knows the exact conditions under which it may or may not use the offered services. If the client takes care that the conditions are met, the correct execution of the service is guaranteed .

The interface that is offered by an object consists of a number of operations that can be performed by the object. For each operation, a contract can be envisioned . The rights of the object that offers the contract are specified by the preconditions. The obligations are specified by the postconditions.

If either party fails to meet the conditions in the contract, the contract is broken. When this happens, it is clear which party broke the contract: either the client did not meet the specified conditions or the supplier did not execute the service correctly. Failure of a precondition or postcondition ”that is, either condition is not true when it should be ”means that the contract is broken. In Eiffel, the only language that implements the design by contract principle, an exception is raised when a precondition or postcondition fails. In this way, the exception mechanism is an integral part of the design by contract principle.

Table 3-1. Rights and obligations in a contract

Party

Obligations

Rights

Customer

Pay two euros

Supply letter with weight less than 80 grams

Specify delivery address within Netherlands

Letter delivered within 4 hours

Express Service Company

Deliver letter within 4 hours

Delivery addresses are within the Netherlands

Receive two euros

All letters weigh less than 80 grams

3.3.6 Messaging in Postconditions

An aspect of postconditions that is very useful for defining interfaces is the fact that a postcondition may state that a certain operation has been called. For example, in the Eclipse framework, which is an open platform for tool integration built by an open community of tool providers (www.eclipse.org), every file is contained in a project. Several builders , which are responsible for keeping associated files up to date, can be linked with the project. An example of a builder is the Java compiler, which compiles a .java file into a .class file. When a file is saved, all builders are informed by calling their incrementalBuild operation. There is no need to uncover how the framework is taking care of this, but there is a need to inform Eclipse developers about this feature. A simple postcondition to the save operation of the class File will suffice:

  context  File::save()  post  : self.project.builders->forAll( b : Builder                                       b^incrementalBuild() ) 

In general, when there is a need to specify dynamic aspects of a model element without revealing the actual implementation, the postcondition of the operations should contain message information. Note again that a postcondition does not specify any statement in the body of the operation. The message may have been sent in a number of different ways. The postcondition merely states that it is sent. Here are some possible example implementations in Java:

 -- get all builders and loop over the collection Builders[] builders = getProject().getBuilders(); for(int i=0; i<builders.size; i++)     builders[i].incrementalBuild(); } -- let some other object take care of calling the builders someObject.takeCareOfBuilders( getProject() ); -- let the project take care of calling the builders getProject().callBuilders(); 

3.3.7 Cycles in Class Models

Many class models reflect cycles in the sense that you can start at an instance, navigate through various associations, and return to an instance of the same class. In the class diagram, this is shown as a cycle of classes and associations. Quite often, these cycles are the source of ambiguities .

The example used in Section 1.4.2, reprinted in Figure 3-2, shows a class diagram with a cycle. In this model, a Person owns a House , which is paid for by taking a Mortgage . The Mortgage takes as security the House that is owned by the Person . Although the model looks correct at first glance, it contains a flaw. Better stated, it is imprecise and can result in ambiguities. The model allows a person to have a mortgage that takes as security a house owned by another person. This is clearly not the intention of the model. We can easily state the necessary invariant with the following piece of OCL code:

  context  Person  inv  : self.mortgages.security.owner                          ->forAll(owner : Person  owner = self) 
Figure 3-2. A cycle in a class model

graphics/03fig02.gif

In general, cycles in a class model should be checked carefully, and any constraints on these cycles should be stated as invariants on one of the classes in the cycle. Especially when the multiplicities in a cycle are higher than one, you need to carefully write the invariant or invariants.

3.3.8 Defining Derived Classes

A view is a well-known concept in relational database systems. In a UML/OCL model, a similar concept exists. This concept is called a derived class . A derived class is a class whose features can be derived completely from already existing classes and other derived classes. The concept of derived classes has been introduced in [Blaha98], and formalized in [Balsters03].

For instance, in the R&L system, it might be useful to define a derived class that holds a transaction report for a customer, as shown in Figure 3-3. The attributes, association ends, and query operations of this class can be defined by the following expressions:

  context  TransactionReportLine::partnerName : String  derive  : transaction.generatedBy.partner.name  context  TransactionReportLine::serviceDesc : String  derive  : transaction.generatedBy.description  context  TransactionReportLine::points : Integer  derive  : transaction.points  context  TransactionReportLine::amount : Real  derive  : transaction.amount  context  TransactionReportLine::date : Date  derive  : transaction.date 
Figure 3-3. A derived class

graphics/03fig03.gif

The class TransactionReport is not completely derived. Its from and until attributes are normal attributes without derivation rules. The other attributes can all be derived:

  context  TransactionReport::name : String  derive  : card.owner.name  context  TransactionReport::balance : Integer  derive  : card.Membership.account.points  context  TransactionReport::number : Integer  derive  : card.Membership.account.number  context  TransactionReport::totalEarned : Integer  derive  : lines.transaction->select( oclIsTypeOf( Earning ) )             .points->sum()  context  TransactionReport::totalBurned : Integer  derive  : lines.transaction->select( oclIsTypeOf( Burning ) )             .points->sum() 

To complete the definition of this class, some invariants are needed. The dates invariant states that the transactions in this report should be transactions between the from and until dates. The cycle invariant states that the transactions in the lines of the report should indeed be transactions for this customer card:

  context  TransactionReport  inv  dates: lines.date->forAll( d  d.isBefore( until ) and                              d.isAfter( from ) )  context  TransactionReport  inv  cycle: card.transactions->includesAll( lines.transaction ) 

3.3.9 Dynamic Multiplicity

Associations in a class diagram can sometimes be imprecise specifications of the system. This is the case when the multiplicity of the association is not fixed, but should be determined based on another value in the system. This is called dynamic multiplicity . An example of this has already been described in Section 1.4.1, where the multiplicity of the association between Flight and Passenger was indicated by the numberOfSeats on the Airplane .

3.3.10 Optional Multiplicity

An optional multiplicity of an association in a class diagram is often a partial specification of what is really intended. Sometimes the optionality is free; that is, in all circumstances there can be either one or no associated object. Quite often, an optional association is not really free. Whether an associated object can or must be present depends on the state of the objects involved. For example, in Figure 3-2, the optional association is not completely free. If a person has a mortgage, he or she must also own a house. This constraint can be specified by the following invariant:

  context  Person  inv  optionality:  mortgages->notEmpty() implies houses->notEmpty() 

In general, when an optional multiplicity is present in the class diagram, you have to use OCL invariants to describe precisely the circumstances under which the optional association may be empty or not empty.

3.3.11 Or Constraints

The class diagram can contain an or constraint between two associations, as shown in Figure 3-4. This constraint means that only one of the potential associations can be instantiated at one time for any single object. This is shown as a dashed line connecting two or more associations (all of which must have at least one class in common), with the string or labeling the dashed line. The multiplicity of the associations must be optional; otherwise , they cannot be empty.

Figure 3-4. Or constraint

graphics/03fig04.gif

Note that the visual representation of an or constraint is ambiguous in certain situations. This is the case when two associations between the same two classes have multiplicity optional at both ends of both associations. The visual or constraint can now be read in two directions. In one direction, it can mean that one person has either a managedProject or a performedProject , but not both. In the other direction, it can mean that one project has either a projectLeader or a projectMember , but not both. Therefore, two different interpretations are possible.

Although the choice of interpretation may seem obvious in this case, you can imagine the consequences if someone makes the wrong interpretation. Specifying the visual or constraint as an OCL constraint resolves the ambiguity problem. When you intend the first interpretation, you should augment the diagram with the following invariant:

  context  Person  inv  : managedProject->isEmpty() or performedProject->isEmpty() 

The invariant stating the second interpretation is:

  context  Project  inv  : projectLeader->isEmpty() or projectMember->isEmpty() 

This example shows the importance of formal constraints and illustrates how they ensure that a model is unambiguous.



Object Constraint Language, The. Getting Your Models Ready for MDA
The Object Constraint Language: Getting Your Models Ready for MDA (2nd Edition)
ISBN: 0321179366
EAN: 2147483647
Year: 2003
Pages: 137

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net