Design by Contract
After this first glance at the terminology and mechanisms
employed in object-oriented computation,
we will look at what I consider to be the contribution of an
object-oriented approach
(and the theme of this book) in a more thematic way.
The term `contract' in the title of this section
is meant to refer to an approach to design that has become
known as design by contract, originally introduced in
[Meyer88], which is closely related to
responsibility-driven design
(see Wirfs-Brock, 1989).
Of course, the reader is encouraged to reflect on alternative interpretations
of the phrase responsibilities in OOP.
The approach captured by the term contract
stresses the importance of an abstract
characterization of what services an object delivers,
in other words what responsibilities an object carries
with respect to the system as a whole.
Contracts
specify in a precise manner the relation between
an object and its `clients'.
Objects allow one to modularize a system in distinct
units, and to hide the implementation details of
these units, by packaging
data and procedures in a record-like structure and defining a message interface to
which users of these units must comply.
Encapsulation refers to the combination of
packaging and hiding.
The formal counterpart of encapsulation is to be found
in the theory of abstract data types.
An abstract data type (ADT) specifies the behavior of an entity in
an abstract way by means of what are called operations
and observations,
which operationally amount to procedures and functions to change or
observe the state of the entity.
See also section [adt-modules].
Abstract data types, that is elements thereof,
are generally realized by employing a hidden state.
The state itself is invisible, but may be accessed and modified
by means of the observations and operations specified by the type.
See slide [1-ADT].
Encapsulation
ADT = state + behavior
Object-oriented modeling
slide: Abstract data types -- encapsulation
Complex applications involve usually complex data.
As observed by [Wirfs89], software developers have reacted to
this situation by adopting more data oriented solutions.
Methods such as semantic information modeling and
object-oriented modeling were developed to accommodate
this need.
See also
sections [methods]
and [behavioral-encapsulation].
Objects may be regarded as embodying an (element of an) abstract data type.
To use an object, the client only needs to know what
an object does, not (generally speaking) how
the behavior of the object is implemented.
However, for a client to profit from the data hiding facilities offered by objects,
the developer of the object must provide an interface
that captures the behavior of the object in a sufficiently abstract way.
The (implicit) design guideline in this respect must be
to regard an object as a server
that provides high level services on request
and to determine what services the application requires of that
particular (class of) object(s).
See slide [1-respons].
Responsibilities
- to specify behavior
what rather than how
Client
client/server model
- makes request to perform a service
Server
- provides service upon request
slide: Responsibilities in OOP
Naturally, the responsibilities of an object cannot
be determined by viewing the object in isolation.
In actual systems, the functionality required
is often dependent on complex interactions between
a collection of objects that must cooperate in order
to achieve the desired effect.
However, before trying to specify these interactions,
we must indicate more precisely
how the communication between a server and a single
client proceeds.
From a language implementation perspective, an object
is nothing but an advanced data structure, even when we
fit it in a client-server model.
For design, however, we must shift our perspective
to viewing the object as a collection of high level,
application-oriented services.
Specifying the behavior of an object from this perspective,
then, means to define what specific information the object is responsible
for and how it maintains the integrity of that information.
See slide [1-contracts].
object = information + responsibilities
Contracts
Behavioral refinement
slide: Contracts and behavioral refinement
The notion of contracts was introduced by [Meyer88]
to characterize in a precise manner what services an object must
provide and what requirements clients of an object must meet
in order to request a service (and expect to get a good result).
A contract specifies both the requirements imposed on a client and the obligations
the server has, provided the requirements are met.
When viewed from the position of a client, a contract reveals
what the client can count on when the requirements are fulfilled.
From the position of the server, on the other hand,
when a client does not fulfill the requirements imposed,
the server has no obligation whatsoever.
Formally, the requirements imposed on the client
and the obligations of the server
can be specified by means of pre- and post-conditions
surrounding a method.
Nevertheless, despite the possibility of formally verifying
these conditions, the designer must specify the right
contract for this approach to work at all.
A problem of a more technical nature the designer
of object-oriented systems faces is how to deal with
inheritance.
Inheritance, as a mechanism of code reuse,
supports the refinement of the
specification of a server.
From the perspective of abstract data types, we must require
that the derived specification refines the behavior of the original server.
We must answer the following two questions here.
What restrictions apply, when we try to refine the behavior of a server
object?
And, ultimately, what does it mean to improve a contract?
Behavioral refinement
Inheritance provides a very general and powerful mechanism for reusing code.
In fact, the inheritance mechanism is more powerful
than is desirable from a type-theoretical perspective.
Conformance -- behavioral refinement
if B refines A
then
B may be used
wherever A is allowed
slide: Behavioral refinement
An abstract data type specifies the behavior of a collection of entities.
When we use inheritance to augment the definition of a given type,
we either specify new behavior in addition to what was given,
or we modify the inherited behavior, or both.
The restriction that must be met when modifying behavior is that the
objects defined in this way
are allowed to be used at all places where objects of the given type
were allowed.
This restriction is expressed in the so-called
conformance rule that states that {\em
if B refines A then B may be used wherever A is allowed}.
Naturally, when behavior is added, this condition is automatically
fulfilled.
See slide [1-conformance].
The conformance rule gives a very useful heuristic for applying inheritance safely.
This form of inheritance is often called `strict' inheritance.
However, it is not all that easy to verify that a class
derived by inheritance
actually refines the behavior specified in a given class.
Partly, we can check for syntactic criteria such as the signature
(that is, type) of the individual methods, but this is definitely not
sufficient.
We need a way
in which to establish that the behavior (in relation to a possible)
client is refined according to the standard introduced above.
In other words we need to know how to improve a contract.
Recall that
from an operational point of view an object
may be regarded as containing data attributes storing information
and procedures or methods representing services.
The question {\em `how to improve a contract?'}
then boils down to two separate questions,
namely: (1) {\em `how to improve the information?'}
and (2) {\em `how to improve a service?'}.
To provide better information is, technically speaking,
simply to provide more information, that is more specific
information.
Type-theoretically, this corresponds to narrowing down the possible
elements of the set that represents the (sub) type.
To provide a better service requires either relieving
the restrictions imposed on the client
or improving the result, that is tightening the obligations of the server.
Naturally, the or must be taken as non-exclusive.
See slide [1-services].
Attributes
refine
Services
Contracts
A better service
- fewer restrictions for the client
- more obligations for the server
slide: Improving services
To improve a contract thus simply means adding more services
or improving the services that are already present.
As a remark,
[Meyer88] inadvertently uses the term subcontract
for this kind of refinement.
However, in my understanding, subcontracting is more a process
of delegating parts of a contract to other contractors
whereas refinement, in the sense of improving contracts,
deals with the contract as a whole,
and as such has a more competitive edge.
Summarizing, at a very high level we may think of objects
as embodying a contract.
The contract is specified in the definition of the class
of which that object is an instance.
Moreover, we may think of inheritance as a mechanism to effect
behavioral refinement,
which ultimately means to improve the contract defining
the relation between the object as a server and a potential client.
Object-oriented modeling
- prototyping, specification, refinement, interactions
OOP = Contracts + Refinements
slide: Object-oriented modeling
To warrant the phrase contract, however, the designer of an object
must specify the functionality of an object in a sufficiently
abstract, application-oriented way.
The (implicit) guideline in this respect is to construct a model
of the application domain.
See slide [1-modeling].
The opportunity offered by an object-oriented approach
to model concepts of the application domain
in a direct way makes an object-oriented style suitable
for incremental prototyping
(provided that the low-level support is available).
The metaphor of contracts provides valid guidelines for the
design of objects.
Because of its foundation in the theory of abstract data types,
contracts may be specified (and verified) in a formal way,
although in practice this is not really likely to occur.
Before closing this section, I wish to mention
a somewhat different interpretation of the notion
of contracts which is proposed by [HHG90].
There contracts are introduced to specify the behavior of collections
of cooperating objects.
See section [formal-coop].