6.7 Constraints and Inheritance


No explicit rules in the UML standard explain whether an expression on a superclass is inherited by its subclasses. To use expressions in a meaningful way in a situation where inheritance plays a role, you need to give them proper semantics. The most widely accepted semantics of inheritance is to ensure that any instance of a subclass must behave the same as any instance of its superclass ”as far as anyone or any program using the superclass can tell. This principle, called Liskov's Substitution Principle [Liskov94], is defined as follows :

Wherever an instance of a class is expected, one can always substitute an instance of any of its subclasses.

OCL expressions adhere to this principle. This section describes the consequences thereof for invariants, preconditions, and postconditions.

6.7.1 Consequences for Invariants

The invariants put on the superclass must always apply to the subclass too; otherwise , the substitution principle cannot be safely applied. The subclass may strengthen the invariant, because then the superclass invariant will still hold. The general rule for invariants is as follows:

An invariant for a superclass is inherited by its subclasses. A subclass may strengthen the invariant but cannot weaken it.

In the model shown in Figure 6-2, we can define for the superclass Stove an invariant that specifies that its temperature must not be hotter than 200 degrees Celsius:

  context  Stove  inv  : temperature <= 200 
Figure 6-2. Inheritance of invariants

graphics/06fig02.gif

It would be dangerous if a subclass ElectricStove could exceed that maximum. For example, suppose that ElectricStove could have a temperature no hotter than 300 degrees Celsius:

  context  ElectricStove  inv  : temperature <= 300 

ElectricStove cannot be used safely in some places where Stove can be used. If you have a location that is fire-safe up to 250 degrees Celsius, you know you can safely put a Stove there. If you place a Stove at this location and the Stove happens to be an ElectricStove , the place may be set on fire ”definitely not a good idea.

Under some circumstances, Liskov's Substitution Principle looks too restrictive . Subclasses may change superclass operations and add their own attributes and operations. In some cases, the superclass invariants should be changed to correspond with these alterations. Whether or not an invariant on a superclass needs to be changed when a new subclass is added depends on the reason for and contents of the invariant.

Consider again the Stove example. If the invariant on the temperature is put on the Stove because its surroundings would catch fire if the temperature were too high, then a new subclass cannot weaken the invariant. Conversely, if the invariant is put on the Stove because of the materials used in the construction, then the invariant might be changed when a new subclass uses more fireproof materials. Thus, using the subclass would be safer, even when the invariant on the temperature is weakened. In this case, we recommend rewriting the temperature invariant so that it includes information about the materials used to construct the stove. This approach states the intention of the invariant more cleanly and removes the need to redefine it for each subclass.

6.7.2 Consequences for Preconditions and Postconditions

When an operation is redefined in a subclass, do the pre- and postconditions of the original operation in the superclass still apply? To find the answer, view the pre- and postconditions as the contract for the operation (see Section 3.3.5). The design by contract principle follows Liskov's Substitution Principle. The rules for pre- and postconditions are as follows:

A precondition may be weakened, not strengthened , in a redefinition of an operation in a subclass.

A postcondition may be strengthened, not weakened, in a redefinition of an operation in a subclass.

The following example illustrates these rules. We define the operation open () for the class Stove in Figure 6-2 as follows:

  context  Stove::open()  pre  : status = StoveState::off  post  : status = StoveState::off and isOpen 

This means that we expect to be able to open a Stove when its status is off . After we have opened the Stove , we expect its status to be off, and isOpen to be true. Suppose for the subclass ElectricStove we redefine open() and give it different pre- and postconditions:

  context  ElectricStove::open()  pre  : status = StoveState::off and temperature <= 100  post  : isOpen 

The precondition of the redefined open() includes an extra condition (temperature <= 100) . The consequence is that ElectricStove does not behave like a Stove any more, because it won't be able to open under the conditions in which a Stove will open (status = StoveState::off) . If we want to make sure that an ElectricStove can be substituted for a Stove , the precondition for the redefined open() cannot be strengthened. We could weaken the precondition, however, because then it will still work as expected for Stove .

The postcondition of open() in ElectricStove is weakened, because the condition status = StoveState::off has been removed. The consequence is that the ElectricStove won't fulfill the expectations of a Stove . After opening, the stove should be in status off . We could have strengthened the postcondition, because then the original expectation would still be met.



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