In the previous chapter we looked at idioms and patterns that resulted from object-oriented software development. In this chapter we will focus on the software engineering of object-oriented systems and issues of design in particular, including the identification of objects and the specification of contractual obligations.
Additional keywords and phrases:
requirements, analysis, implementation, design as transition,
CRC cards, responsibilities, heuristics,
contractual obligations,
validation
In this section we will look at a variety of existing methods and the tools they offer. We do not discuss the tools and diagram techniques used in any detail. However, we will discuss the Fusion method in somewhat more detail. Fusion is a strongly systematic approach to object-oriented software development that integrates various concepts and modeling techniques from the other methods, notably OMT, Booch OOD, Objectory and CRC. We will discuss the process view underlying Fusion and sketch the models it supports in relation to the other methods. For the reader this section may supply an overview and references needed for a more detailed study of a particular method or tool.
A recent development is the Unified Modeling Language (UML), which has been approved as a standard in 1998. UML brings together the models and notations featured by the various methods. Jim Rumbaugh, Grady Booch and Ivar Jacobson, all leading experts in object-oriented development, joined forces to achieve this.
The importance of such a standardization can hardly
be overemphasized.
However, it must be noted that UML does not provide
a method, in the sense of delineating the steps that
must be taken in the development of a system.
UML itself may be regarded as a toolbox, providing
notations and modeling techniques that may be deployed
when needed.
A brief overview of UML is given in
UML.
An excellent introduction to UML, including advice
how to apply it in actual projects may be found
in
In contrast to the model used in analysis,
both the design model and the implementation model are more
solution oriented than domain oriented.
The implementation model is clearly dependent on the available
language support.
Within a traditional life-cycle, the design model may be seen as
a transition from analysis to implementation.
The notion of objects may act as a unifying factor,
relating the concepts described in the analysis document
to the components around which the design model is built.
However, as we have noted, object-oriented development
does not necessarily follow the course
of a traditional software life-cycle.
Alternatively, we may characterize the function of the
design document as a justification of the choices
made in deciding on the final architecture of the system.
This remark holds insofar as an object-oriented approach
is adopted for both design and implementation.
However, see
In
systematic comparison
Objectory | OMT | Booch | CRC | Fusion | |
---|---|---|---|---|---|
development | + | +- | - | x | + |
maintenance | + | +- | + | - | + |
structure | +- | +- | + | + | + |
management | + | +- | +- | - | + |
tool support | +- | +- | +- | - | + |
An interesting architectural issue is, how may we
provide for future extensions of the system?
How easily can we reuse the design and the code for
a system supporting different kinds of accounts,
or different input or output devices?
And how can we establish that the objects, as
identified, interact as desired?
analyze a little,
design a little,
implement a little,
test a little ...
For dynamically checking the invariance condition,
a test should be executed when evaluating the
constructor
and before and after each method
invocation.
While a method is being executed, the invariant
need not necessarily hold,
but it is the responsibility of a method to
restore the invariant when it is disrupted.
In case object methods are recursively applied,
the invariant must be restored when returning
to the original caller.
An alternative approach to incorporating assertions
in a class description is presented in
A pre-condition limits the cases that
a supplier must handle!
To establish that the contract of a derived class refines the contract
of the base class it suffices to verify that the following rules
are satisfied.
See slide 3-inheritance.
Assigning responsibilities
Design is to a large extent a matter of creative thinking.
Heuristics such as performing a linguistic scan
on the requirements document for finding objects
(nouns), methods (verbs) and attributes (adjectives)
may be helpful, but will hopelessly fail
when not applied with good taste and judgement.
Not surprisingly, one of the classical techniques
of creative writing, namely the shoe-box method,
has reappeared in the guise of an object-oriented
development method.
The shoe-box method consists of writing fragments
and ideas on note cards and storing them in a (shoe) box,
so that they may later be retrieved and manipulated
to find a suitable ordering for the
presentation of the material.
To find a proper decomposition into objects,
the method creates for each potential
(object) class a so-called CRC card,
which lists the Class name, the Responsibilities
and the possible Collaborators of the proposed class.
In a number of iterations, a collection of cards
will result that more or less reflects the
structure of the intended system.
According to the authors (see Beck and Cunningham, 1989), the method
effectively supports the early stages of design,
especially when working in small groups.
An intrinsic part of the method consists
of what the authors call dynamic simulation.
To test whether a given collection of cards
adequately characterizes the functionality of the intended
system, the cards may be used to simulate
the behavior of the system.
When working in a group, the cards may be distributed
among the members of the group, who participate
in the simulation game according to their cards.
See slide 3-crc.
Object-oriented thinking
OO design with CRC cards
Example -- ATM (2)
Banking model
transaction keeps balance database deposit money withdraw money
card-reader validation cash-dispenser performs transfer account keeps audit info database Interaction classes
event signals insertion transaction decodes strip
event emits cash
event retrieves account transaction records transaction account authorization database Object roles and interaction
Objects rarely live in isolation.
In a system of some complexity,
a number of different kinds of object
classes may usually be distinguished.
Each kind of class may be regarded as playing
a specific role in the system.
For example, when considering our ATM,
classes such as card-reader and cash-dispenser
are of a completely different kind, and play a completely
different role, than the classes
account and database for instance, or the classes
event and transaction.
Often it will take some
experimentation to decide how control must
be distributed among the objects comprising the system.
Although
the framework chosen for the development of the system
may partly determine the control model,
there will usually be ample choice left for the designer of
the system to define the interactions between objects.
Object roles
Contracts
subsections:
To establish the interaction between objects in a more
precise way, we need a notion of contracts,
specifying the requirements a client must comply with
when requesting a service from a server object.
Our notion of contracts will be based on the notion of types.
In the universe of programming,
types are above all a means to create
order and regularity.
Also, in an object-oriented approach,
types may play an important role in
organizing classes and their relationships.
As observed in Contractual obligations
client supplier pre-condition obligation benefit post-condition benefit obligation Specifying contractual obligations
A formal specification of the behavior of an object may
be given by defining a pre-condition
and post-condition for each method.
The pre-condition of a method specifies in a logical
manner what
restrictions the client invoking a particular method
is obliged to comply with.
When the client fails to meet these requirements
the result of the method will be undefined.
In effect, after the violation of a pre-condition
anything can happen.
Usually, this means that the computation
may be aborted or that some other means of error-handling
may be started.
For instance, when the implementation language
supports exceptions an exception handler may be invoked.
The post-condition of a method states what
obligations the server object has when executing the
method, provided that the client's request satisfies
the method's pre-condition.
Apart from specifying a pre-condition and post-condition
for each method publicly supported by the class,
the designer of the class may also specify a class invariant,
to define the invariant properties of the state of
each instance of the class.
A class annotated with an invariant
and pre- and post-conditions for
the methods may be regarded as a contract,
since it specifies precisely (in an abstract way)
the behavioral conformance conditions of the object
and
the constraints imposed on the interactions
between the object and its clients.
See slide 3-obligations.
Assertions -- formal specification
Language support
Interfaces
Contracts may be used to
document the method interface of a class.
Pre- and post-conditions allow the class designer
to specify in a concise manner the functional
characteristics of a method,
whereas the use of natural language often leads to
lengthy (and imprecise) descriptions.
Below, an example is given of a contract specifying an account.
public:
account();
// assert( invariant() );
virtual float balance() { return _balance; }
void deposit(float x); to deposit money
// require( );
// promise( balance() old_balance x && invariant() );
void withdraw(float x); to withdraw money
// require( x balance() );
// promise( balance() old_balance x && invariant() );
bool invariant() { return balance() 0; }
protected:
float _balance;
};
System development
System development
Realization
public:
account() { _balance = 0; assert(invariant()); }
virtual float balance() { return _balance; }
void deposit(float x) {
require( x >= 0 ); // check precondition
hold(); // to save the old state
_balance += x;
promise( balance() == old_balance + x );
promise( invariant() );
}
void withdraw(float x) {
require( x <= _balance ); // check precondition
hold(); // to save the old state
_balance -= x;
promise( balance() == old_balance - x );
promise( invariant() );
}
virtual bool invariant() { return balance() >= 0; }
protected:
float _balance;
float old_balance; // additional variable
virtual void hold() { old_balance = _balance; }
};
Refining contracts
Contracts provide a means to specify the behavior of an object
in a formal way by using logical assertions.
In particular, a contract specifies the constraints involved
in the interaction between a server object and a client invoking a method
for that object.
When developing a refinement subtype hierarchy we need to establish
that the derived types satisfy the constraints imposed by
the contract associated with the base type.
Refining a contract -- state responsibilities and obligations
Refining a method -- like improving a business contract
However, before we delve into the formal foundations of
object-oriented modeling,
we will first look at an example of application
development and explore
the design space of object-oriented languages and
system implementation techniques.
These insights will enable us to establish
to what extent we may capture a design in formal
terms, and what heuristics are available to accomplish
the tasks remaining in object-oriented development.
Summary
draft version 0.1 (15/7/2001)