Behavioral refinement

\oopindex{refinement} Designing an object oriented system requires to identify the objects needed to model the problem domain and to assess their responsibilities and (possible) cooperation with other objects. See  [WWW90] and also section \ref{dig:oom}. From this perspective objects may be regarded as entities that provide a service in response to a message. The services an object is willing to provide are listed in the external method interface of (the class of) an object. The set of services provided by an object is called a contract in  [Meyer88]. A contract specifies the obligations of both the client and the server, that is when the client complies to the restrictions imposed by the server the server has the obligation to deliver the service. Inheritance has been characterized as a means to specialize concepts or classes of objects. Viewing an object as an entity that provides a service, the question arises of what it means to specialize or refine a service. And a fortiori, what it means to refine a contract. The intuitive answer to this question, for a service, is that refinement means to provide a better service, a service that imposes less restrictions on the client and (yet) delivers better results. And for a contract, taken as a set of services, a better contract means a contract that comprises more services and with regard to the services already available, the individual services must be at least as good or better. Perhaps somewhat surprisingly, these seemingly vague notions may be given a precise formal underpinning. In the following we will sketch the formal interpretation of the notions of refining a service and a contract (that is a set of services). We will start with a syntactic characterization of refinement, by showing how the subtype relation introduced previously may be regarded as characterizing the refinement of a service. Recall that to define the subtype relation between objects we took objects as records consisting of attributes and functions, and we characterized the subtype relation componentwise by defining a subtype relation for attributes and functions.

Attributes

Each of the components of an object may be regarded as providing a service. An attribute may be regarded as providing the service of giving information about the state of an object. The type of the attribute tells what kind of information the attribute may provide. Taken as a service, an attribute (type) is improved upon by another attribute (type) if the latter gives more precise information concerning the state of the object. Hence the service refinement relation between attribute (types) is exactly the subtype relation between simple types, that we have characterized as the subset relation. As an example, the attribute age:[0..120] gives less information than the attribute age:[65..120]. Applied to people, the latter simply tells more.

Functions

The service provided by a function may be characterized by the input/output behavior of the function. Syntactically, the domain type (the types of the parameters of the function) specifies the restrictions imposed on the client -- the user of the function; and the result type of the function specifies the restrictions to which the function delivering the service must conform. Viewing a function as a service, a function f' is a better function then a function f if the result delivered by f' is better than the result delivered by f and if the restrictions imposed on the client by f' are less severe than the restrictions imposed by f. It is easy to see, this interpretation agrees with the subtyping rule for functions that states that f':%s' -> %t' <= f:%s->%t if %s <= %s' and %t' <= %t. As an example consider the functions [D f:[9..11] -> [1..6] f':[8..12] -> [2..5] D] We have that f' <= f since the domain of f' allows more freedom than the domain of f ( because [9..11] <= [8..12]) and the result of f' is more carefully delineated than the result of f (because [2..5] <= [1..6]). It is left to the reader to think of a daily life interpretation for these functions.

Objects

In a similar fashion, we may interpret the subtype relation between objects (taken as records) as a refinement relation between sets of services. Namely, a set of services may be improved by adding new services or by refining one or more of the already available services. Having a purely syntactic notion of refinement however is not sufficient to characterize the behavior of objects in an adequate way, as the following example will show. Suppose that we have defined a class Person with an attribute age and a function set_age, and that we derive a class Retiree by restricting the range of the attribute age. [D Person = { age:[0..120], set_age:[0..120] -> Person } Retiree = Person + { age:[65..120] } D] According to our rules, the class Retiree refines the class Person. However, when we apply set_age with argument 40 to a Retiree we no longer have a Retiree since we are violating the restrictions imposed by the attribute age of a Retiree. Now we could redefine Retiree as [D Retiree = { age:[65..120], set_age:[65..120] -> Retiree } D] but then we no longer have that Retiree <= Person since [D] set_age_{Retiree} \not<=_{subtype} set_age_{Person} because [0..120] \not<= [65..120]. A solution to this problem is to introduce a function such as inc_age that allows to increment the age of a given person, be it a Person or a Retiree.

Contracts

The example shows that the design of a hierarchy of classes is not altogether a trivial matter. Moreover, it shows that syntactic criteria are insufficient to decide on whether a hierarchy of classes provides the right abstraction. Type checking alone does not preclude such errors. The detection of a violation such as that exemplified by the function set_age will occur only at run-time. In order to characterize the contract offered by an object in a more formal way  [Meyer88] proposes to use assertions to specify the behavioral characteristics of a method. The use of an assertion language to specify the pre- and post-conditions of a method provides a powerful means to lay down the restrictions to which the client of a method must conform and to characterize the obligations for the provider of the service (when these restrictions are met). In the actual development of systems, the use of pre- and post-conditions is of help in locating the source of errors. When a violated pre-condition is encountered the error must be looked for in the code of the client. Contrarily, the violation of a post-condition hints at a bug in the supplier. Now, to establish what it means to refine a contract, recall that refining a service means to give better service while alleviating the restrictions imposed on the client. In accordance with the pattern established for the refinement of a function, to improve a method -- of which the behavior is (partially) described by assertions -- means to accept weaker pre-conditions and to guarantee stronger post-conditions. Refining a contract thus consists of adding new services or improving upon existing ones. See also  [HHG90] for contracts specifying behavioral compositions of objects.