The Object Constraint Language: Getting Your Models Ready for MDA (2nd Edition)
The diagram in Figure 2-1 does not express all relevant information about the R&L system. The following sections provide examples of additional information that cannot be expressed in the diagram, but should be specified in OCL expressions. 2.2.1 Initial Values and Derivation Rules
A very basic addition to the diagram shown in Figure 2-1 is to include rules that state initial values for attributes and association ends. Initial value rules can be expressed very simply. First, you indicate the class that holds the attribute or association end. This class is called the context . Then you write the expression that states your initial value rule. For instance, a loyalty account will always be initialized with zero points, and a customer card will always be valid at the moment it is issued: context LoyaltyAccount::points init : 0 c ontext CustomerCard::valid init : true Another small but indispensable part of the model is the specification for determining the value of derived elements. A model may contain derived attributes and derived associations. For both, a so-called derivation rule can be specified. Again, the context of the expression is the class that holds the attribute or association end. The expression after the context states the derivation rule. For instance, the derived attribute printedName of CustomerCard is determined based on the name and title of the card owner. c ontext CustomerCard::printedName derive : owner.title.concat(' ').concat(owner.name) In this example, the printedName is the concatenation of the title of the Customer who owns the card, its name, and a space between both strings (e.g., "Mr. Johnson"). 2.2.2 Query Operations
Query operations are operations that do not change the state of the system; they simply return a value or set of values. The definition of the result of query operations cannot be given in a diagram. In OCL, this can be defined by writing a body expression . The operation name, parameters, and return type (its signature) are given as context. For instance, suppose the class LoyaltyProgram has a query operation getServices , which returns all services offered by all program partners in this program: context LoyaltyProgram::getServices(): Set(Service) body : partners.deliveredServices->asSet() In this example, the association-end deliveredServices of ProgramPartner that holds a set of Services is used. For all instances of ProgramPartner that are associated with the LoyaltyProgram instance of which the operation getServices is called, these sets of Services are collected and combined into one set. This set is the result of the query operation. In the body expression, the parameters of the operation may be used. For instance, suppose you need a more refined version of the getServices operation. This operation takes as a parameter a program partner object and returns the services delivered by the parameter object, if it is a partner in this program. In this case, the refined operation can be specified as follows : context LoyaltyProgram::getServices(pp: ProgramPartner) : Set(Service) body : if partners->includes(pp) then pp.deliveredServices else Set endif The result of this query operation is the set of Services held by the parameter pp , or an empty set if the parameter instance of ProgramPartner is not a partner of the LoyaltyProgram for which the query operation is called. Note that there is no difference between a derived attribute and a query operation that has no parameters, other than the notation. The query operation needs to be written using parentheses. 2.2.3 Defining New Attributes and Operations
Although most elements in the model are introduced in the UML diagrams, attributes and operations can be added to the model using an OCL expression. The context is the class to which the attribute or operation is added. An attribute that has been defined in this manner is always a derived attribute. The expression that defines the attribute includes the name and type of the attribute, and the derivation rule. For instance, we might want to introduce an attribute called turnover in class LoyaltyAccount , which would sum the amount attributes of the transactions on the account. This attribute can be defined by the following expression. The part after the equal sign states the derivation rule for the new attribute: context LoyaltyAccount def : turnover : Real = transactions.amount->sum() The derivation rule results in a single real number that is calculated by summing the value of the amount attribute in all transactions associated with the LoyaltyAccount instance that holds the newly defined attribute. An operation that has been defined in an OCL expression is always a query operation. The expression after the equal sign states the body expression. For example, we might want to introduce the operation getServicesByLevel in the class LoyaltyProgram . This query operation returns the set of all delivered services for a certain level in a loyalty program: context LoyaltyProgram def : getServicesByLevel(levelName: String): Set(Service) = levels->select( name = levelName ).availableServices->asSet() The result of the body expression is calculated from a selection of the levels associated with the instance of LoyaltyProgram for which the operation getServicesByLevel is called. It returns only the services available from the ServiceLevel whose name is equal to the parameter levelName . The reason for using the asSet operation is given in Section 2.4.2. |