Principles of Object-Oriented Software Development
[] readme course preface 1 2 3 4 5 6 7 8 9 10 11 12 appendix lectures resources

talk show tell print

Software engineering perspectives

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.


Software engineering perspectives

3


Additional keywords and phrases: requirements, analysis, implementation, design as transition, CRC cards, responsibilities, heuristics, contractual obligations, validation


slide: Software engineering perspectives

First we will explore what methods are available to guide us in the development of object-oriented systems. Then we will look more closely at the heuristics of actual design. After establishing what is involved in specifying contractual obligations, we will discuss what is needed for a more formal approach to object-oriented development.

Development methods

subsections:


Object-oriented software development is a relatively new technology. Consequently, ideas with respect to methodologies supporting an object-oriented approach are still in flux. Nevertheless, a plethora of methods and tools does exist supporting object-oriented analysis and design. See slide 11-methods.

Unified Modeling Language -- standard notation

UML


  • class diagrams, object interaction, packages, state and activity

slide: Software development methods

Some of these methods (and corresponding tools) directly stem from a more conventional (read structured) approach to software development. Others are more radical and propose new tools to support the decomposition principles underlying object-oriented technology. Naturally, those who wish to make a gradual shift from conventional technology to adopting an object-oriented approach may benefit from methods that adapt familiar techniques to the new concepts.

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  [Fowler97].

Structured methods

Initially, structured methods (which were developed at the beginning of the 1970s) were primarily concerned with modeling processes in a modular way. Based on software engineering principles such as module coupling and cohesion, tools were developed to represent the structure of a design (within what we have previously called the procedural or modular paradigm); see, for example,  [Yourdon79]. Apart from diagrams to describe the modular architecture of a system (such as structure charts and process specifications), structured methods also employ data flow diagrams to depict processes and the flow of data between them, and hierarchy diagrams to model the structure of the data involved. See slide 11-structured.

Structured methods

tools


  • structure chart
  • process specification
  • dataflow diagrams
  • hierarchy diagram
  • entity-relationship diagrams
  • data dictionary
  • state transition diagram

slide: Tools for a structured approach

Later, structured methods were extended to encompass analysis, and the focus shifted to modeling the data by means of entity-relationship diagrams and data dictionaries. Also, state transition diagrams were employed to represent the behavioral aspects of a system. As observed in  [Fichman], in the late 1970s and early 1980s, planning and modeling of data began to take on a more central role in system development, culminating in data oriented methodologies, such as information engineering (which may be regarded as precursors to object-oriented methods). Information engineering, however, is primarily concerned with analysis and strategic planning. In addition to the modeling techniques mentioned, tools were developed to model the information needs of an enterprise and to perform risk analysis. Also, extensions to the data dictionary were proposed in order to have an integrated repository, serving all phases of the development. Currently, repository-based techniques are again of interest since, in combination with modern hypermedia technology, they may serve as the organizational basis for reuse.

Perspectives of modeling

Understanding a problem domain may be quite demanding. Understanding is even more difficult when the description of the domain is cast in some representation pertaining to the solution domain. An object-oriented approach is said to require less translation from the problem domain to the (software) solution domain, thus making understanding easier. Many proponents of an object-oriented approach, however, seem to be overly optimistic in their conception of the modeling task. From an epistemological point of view, modeling may be regarded as being essentially colored by the mechanisms that are available to express the model. Hence, rather than opposing the functional and object-oriented approach by claiming that an object-oriented approach aims at modeling reality, I would prefer to characterize the distinction in terms of (modeling from) a different vernacular, a different perspective due to different modeling mechanisms. In other words, a model is meant to capture some salient aspects of a system or problem domain. Dependent on what features are considered as most important, different means will be chosen to construct a model. Even within the confines of an object-oriented approach, there appear to be radically different perspectives of the modeling required in the various phases of the software life-cycle.

Modeling reality -- vernacular

  • requirements -- use cases
  • analysis -- domain concepts
  • design -- system architecture
  • implementation -- language support

Design model -- system oriented

  • provides a justification of the architecture

slide: Perspectives of modeling

An important contribution of  [Jacobs92] is the notion of use cases that describe the situations in which a user actually interacts with the system. Such a (use case) model is an important constituent of the requirements document, since it precisely describes what the system is intended for. For the purpose of analysis, it may be helpful to develop a more encompassing (conceptual) model of the problem domain. The advantage of such an approach is that the actual system may later easily be extended due to the generality of the underlying analysis model.

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  [Hend90] for a variety of combinations of structured, functional and object-oriented techniques.

Dimensions of modeling

When restricting ourselves to design models, we may again distinguish between different modeling perspectives or, which is perhaps more adequate in this context, dimensions of modeling.

In  [Rum91], it is proposed to use three complementary models for describing the architecture and functionality of a system. See slide 3-dimensions.


Dimensions of modeling -- OMT

  • object model -- decomposition into objects
  • dynamic model -- intra-object state changes
  • functional model -- object interaction (data-flow)

Model of control

  • procedure-driven, event-driven, concurrent

slide: The OMT method

The OMT method distinguishes between an object model, for describing the (static) structure of object classes and their relations, a dynamic model, that describes for each object class the state changes resulting from performing operations, and a functional model, that describes the interaction between objects in terms of a data-flow graph. An important contribution of  [Rum91] is that it identifies a number of commonly used control models, including procedure-driven control, event-driven control and concurrent control. The choice for a particular control model may deeply affect the design of the system. The OMT approach may be called a hybrid method since it employs non object-oriented techniques for describing intra-object dynamics, namely state-charts, and a functional approach involving data-flow diagrams, for describing inter-object communication.

Coherent models

The OMT object model, however, only captures the static structure of the system. To model the dynamic and functional aspects, the object model is augmented with a dynamic model, which is given by state diagrams, and a functional model, which is handled by data flow diagrams. From a formal point of view this solution is rather unsatisfactory since, as argued in  [Hayes91], it is hard to establish the consistency of the combined model, consisting of an object, dynamic and functional model.

Model criteria -- formal approach

  • unambiguous -- single meaning
  • abstract -- no unnecessary detail
  • consistent -- absence of conflict

slide: Coherent models -- criteria

Consistency checking, or at least the possibility to do so, is important to increase our belief in the reliability (and reusability) of a model. To be able to determine whether a model is consistent, the model should be phrased in an unambiguous way, that is, in a notation with a clear and precise meaning. See slide 3-coherent. Also, to make the task of consistency checking manageable, a model should be as abstract as possible, by leaving out all irrelevant details. To establish the consistency of the combined model, covering structural, functional and dynamic aspects, the interaction between the various models must be clearly defined.

Requirements engineering -- Fusion

The Fusion method is presented in  [Fusion] as a second generation object-oriented method. The phrase second generation is meant to indicate that the method transcends and incorporates the ideas and techniques employed in the early object-oriented methods. Above all, the Fusion method focuses on a strongly systematic approach to object-oriented software development, with an emphasis on the process of development and the validation of the consistency between the models delivered in the various phases of a project. The software life-cycle model underlying Fusion is the traditional waterfall model, consisting of the subsequent phases of analysis, design and implementation. Each phase results in a number of models describing particular aspects of the system. See slide 11-fusion. A data dictionary is to be kept as a means to unify the terminology employed in the various phases.

Analysis -- Fusion

Fusion


  • Object Model -- concepts and relations
  • LifeCycle Model -- sequences of operations
  • Operation Model -- semantics of system operations

Design -- data dictionary

data dictionary


  • Object Interaction Graph -- functional dependencies
  • Visibility Graphs -- communication structure
  • Class Descriptions -- attributes and methods
  • Inheritance Graphs -- subtype refinement

Implementation -- validation

validation


  • System Lifecycle -- state machines
  • Class Descriptions -- coding, performance

slide: The Fusion method

The models produced as the result of analysis, design and implementation serve to document the decisions made during the development. Each of the phases covers different aspects of the system. Analysis serves to document the system requirements from a user perspective. The Fusion method describes how to construct an Object Model that captures the basic concepts of the application domain. These concepts are represented as entities or objects and are connected by relations, similar to entity-relationship diagrams employed in semantic modeling. Analysis also results in an Operation Model, describing the semantics of the operations that may be performed by a user by means of pre- and post-conditions, in a formal manner. In addition, Fusion defines a Lifecycle Model that describes, by means of regular expressions, which sequences of operations are allowed. Design may be considered as the transition between analysis and implementation. During design, decisions are made with respect to the realization of the system operations identified during analysis. Design according to the Fusion method results in an Object Interaction Graph, that for each system operation describes which objects are involved and which methods are invoked. Fusion also allows one to label the arrows representing method calls in the interaction diagram with sequencing information. In addition, design involves the construction of Visibility Graphs, indicating the attribute and method interface for each object, Class Descriptions, defining the attributes and methods of objects, and Inheritance Graphs, specifying the subtype refinement relation between classes. Implementation is considered in the Fusion method as a phase in which to work out the details of the decisions taken during analysis and design. It results in a System Lifecycle description for each object identified in the Object Model, in the form of a finite state machine, and precise Class Descriptions, in the form of (preferably) efficient code.

Validation

An important aspect of the Fusion method is the validation of the completeness and consistency of the collection of models. Completeness, obviously, is a relative matter and can only be established with respect to explicitly stated user requirements. However, the models developed in a particular phase impose additional requirements upon the efforts engaged in the later phases and in the end maintenance. Consistency involves verifying whether the various models are not contradictory. For both development and validation, the data dictionary plays an important role, as a common point of reference.

Methods for analysis and design -- a comparative study

In  [Fichman] a comparative review of a selected number of object-oriented analysis and design methods is given. Criteria for selection were the availability of documentation and acceptance in the object-oriented community, measured in terms of refereed articles. Paraphrasing  [Fichman] again: As with traditional analysis, the primary goal of object-oriented analysis is the development of an accurate and complete description of the problem domain. The three analysis models described in  [Fichman] share a number of diagram techniques with both structured methods and methods for object-oriented design. However, the method proposed in  [Shlaer88] in particular reflects the domain-oriented focus of analysis. A similar focus on domain requirements and analysis may be found in the Objectory method. See slide 11-compar-1. Objectory is one of the methods that has inspired Fusion, in particular because it presents a systematic approach to the process of software development. The Objectory method centers around use case analysis. Use case analysis involves a precise description of the interaction between the user of a system and the components representing domain-specific functionality. The Objectory method gives precise guidelines on how to proceed from the identification of use cases, which include user interface aspects, to their realization in the subsequent phases of design and implementation. Objects are called blocks in Objectory. Use case analysis corresponds in a loose way with the identification of system operations in Fusion.

Objectory -- systematic process

  • requirements -- use cases, domain object model, user interface
  • analysis -- subsystems
  • design, implementation -- block model, interaction diagrams

OMT -- few rules for discovering inconsistencies

  • analysis -- object model, dynamic model, functional model
  • design, implementation -- heuristics to implement analysis models

Booch -- descriptive

  • diagrams -- class, object, timing, state, module, process

CRC -- exploratory

  • analysis, design -- class, responsibilities, collaborators

Formal methods

  • operations -- pre- and post-conditions

slide: Comparison of methods (1)

There is a close correspondence between the OMT object model and the analysis object model of Fusion. Both OMT and Fusion employ extended entity-relationship diagrams. Also, the dynamic model of OMT reoccurs in the Fusion method, albeit in a later phase. The functional model of OMT, which has the form of a dataflow diagram, is generally considered to be inappropriate for object-oriented analysis. Instead, Fusion employs a model in which the semantics of system operations are captured by means of formal pre- and post-conditions. In  [Fusion], OMT is characterized as a very loose method, giving few rules for discovering inconsistencies between the various models and lacking a clear view with respect to the process of development. OMT is strongly focused on analysis, giving nothing but heuristics to implement the models that result from analysis. However, what is called the light-weight Fusion method almost coincides with OMT. A lack of detailed guidelines for the process of software development is also characteristic of the Booch OOD method. Booch offers a wealth of descriptive diagrams, giving detailed information on the various aspects of a system, but offers merely heuristics for the actual process of development. The CRC method must be regarded primarily as a means to explore the interaction between the various objects of a domain. It is powerful in generating ideas, but offers poor support for documenting the decisions with respect to the objects and how they interact. Formal methods have been another important source of inspiration for the Fusion method. The description of system operations during analysis employs a characterization of the functionality of operations that is directly related to the specification of operations in model-based specification methods such as VDM and Z. See section formal-coop. The Fusion method may be regarded as being composed of elements of the methods mentioned above. It shares its object model with OMT, its approach to the characterization of system operations with formal methods, its focus on object interaction with CRC and its explicit description of classes and their relations with Booch. See slide 11-compar-2.

systematic comparison


ObjectoryOMTBooch CRC Fusion
development++--x+
maintenance++-+-+
structure+-+-+++
management++-+--+
tool support+-+-+--+

slide: Comparison of methods (2)

In comparison with these methods, however, it provides a much more systematic approach to the process of development and, moreover, is explicitly concerned with issues of validation and consistency between models. In addition,  [Fusion] claim to provide explicit semantics for their various models, whereas the other methods fail to do so. However, it must be remarked that the Fusion method remains somewhat obscure about the nature of system operations. System operations are characterized as asynchronous. Yet, if they are to be taken as methods, such operations may return a result, which is quite hard to reconcile with their asynchronous nature. The claim that the models have a precise semantics, which is essential for tool support, must be substantiated by providing an explicit semantics in a formal manner! With regard to the process of development, both Objectory and Fusion provide precise guidelines. The CRC method may be valuable as an additional exploratory device. For maintenance, the extent to which a method enforces the documentation of design decisions is of utmost importance. Both the Objectory and Booch method satisfys this criterion, as does the Fusion method. OMT is lacking in this respect, and CRC is clearly inadequate. Whether a method leads to a good object-oriented design of the system architecture, depends to a large extent upon the ability and experience of the development team. Apart from Fusion, both the Booch method and CRC may be characterized as purely object-oriented, whereas Objectory and OMT are considered to be impure. A strongly systematic approach to the process of development is important in particular from the point of view of project management. Project management support entails a precise definition of the deliverables associated with each phase, as well as an indication of the timing of their deliverance and validation. Both the OMT method and Booch are lacking in this respect, since they primarily provide techniques to develop descriptive models. Clearly, CRC lacks any support for project management. Tool support is dependent on the existence of a well-defined semantics for the models employed. For both Objectory and OMT commercial tools are available, despite their loosely specified semantics. The Fusion diagramming techniques are also supported. For CRC, tool support is considered to be useless. The success of the method depends upon flexibility, the ease with which new ideas can be tried, a flexibility which even hypertext cannot offer, according to its authors.

Identifying objects

subsections:


Object-oriented design aims at describing a system in terms of objects (as the primary components) and the interaction between them. Motivated by the wish to arrive at stable abstractions, object-oriented design is often characterized as modeling reality, that is the application domain. However, many applications require, at least partly, a system-oriented view towards design, since they involve system artifacts for which there exist no clearly identifiable counterparts in the application domain. As an example, think of a window-based system. Many of the items (widgets) introduced in such a system belong to an artificial reality, which at best is only vaguely analogous with reality as we normally understand it. Irrespective of whether the design is intended as a preliminary study before the implementation or as a post hoc justification of the actual system, the most important and difficult part of design is the identification of objects and the characterization of their role in the system and interaction with other objects. As observed in  [McGregor92], object-oriented design is best seen as class oriented, that is directed towards the static description of (classes of) objects, rather than a description of the dynamic interaction between actual objects. In section prototypes, we will discuss class-less languages which are well suited for exploratory programming. However, from the perspective of design, we are more interested in a (static) abstract specification of the components that constitute the system.

Object-oriented design -- decomposition into objects

  • application/system/class oriented

Identifying objects -- responsibilities

  • data/procedure oriented

Layers of abstraction

  • components, subsystems, frameworks

slide: Object-oriented design

In comparison with a functional approach, object-oriented design is clearly data oriented. However, although a data-oriented approach may provide a first guideline in developing the system, the primary concern in object-oriented design should be the responsibilities of an object rather than how it acts as a data manager, so to speak. For larger systems, the complexity of the design may necessitate the introduction of additional layers of abstraction. Apart from objects, which must be regarded as the basic components of a system, we may need to isolate subsystems, consisting of a number of related object classes. When we have developed a subsystem that can be used in a variety of contexts, such a subsystem may be used as a framework. A framework is generally not only a collection of classes but must also be seen as an approach or method in its own way, since it usually imposes additional constraints on the development. For example, most development environments for window-based applications provide a framework consisting of a number of predefined classes and functions, and guidelines or recipes that prescribe how to use or adapt these classes and functions. Also, most frameworks impose a specific control model, such as the event-driven control model imposed by window programming environments.

Modeling heuristics

Following  [Booch86], we may characterize objects as `crisp' entities that suffer and require actions. From the perspective of system development, objects must primarily be regarded as computational entities, embodying the means by which we may express a computation. Modeling a particular problem domain, then, means defining abstractions in terms of objects, capturing the functional characteristics of that domain. The question is, how do we arrive at such a model?

Objects -- crisp entities

  • object = an entity that suffers and requires actions

The method:

  • [1] Identify the objects and their attributes
  • [2] Identify operations suffered and required
  • [3,4] Establish visibility/interface

slide: The Booch method

In  [Booch86], a straightforward method of object oriented development is proposed, which consists of the successive identification of objects and their attributes, followed by a precise characterization of the interobject visibility relations. In  [Booch91], a shift of emphasis has occurred towards determining the semantics of an individual object and the interaction between collections of objects.

Heuristics

  • model of reality -- balance nouns (objects) and verbs (operations)

Associations

  • directed action -- drives, instructs
  • communication -- talks-to, tells, instructs
  • ownership -- has, part-of
  • resemblance -- like, is-a
  • others -- works-for, married-to

slide: Heuristics for modeling

As a heuristic to arrive at the proper abstractions of the problem domain (in terms of object classes),  [Booch86] proposes scanning the requirements document for nouns, verbs and adjectives, and using these as initial suggestions for respectively objects, and operations and attributes belonging to objects (see slide 3-heuristics). This technique has been adopted and augmented by a number of other authors, among which  [WWW90] and  [Rum91]. For example,  [WWW90] illustrate the technique in fine detail in several examples, including the design of an automated teller machine and a document processing system. In addition to the interpretation of nouns as possible objects, verbs as possible operations on objects, and adjectives as possible attributes of objects,  [Rum91] suggest this technique to determine other relations and associations between object classes as well. For instance, a model of control and object interaction may be suggested by phrases indicating directed action or communication. Similarly, structural issues, such as whether an object owns another object or whether inheritance should be used, may be decided on the basis of resemblance or subordination relations.

Example -- ATM (1)

The example of an automated teller machine discussed in  [WWW90] nicely illustrates a number of the notions that we have thus far looked at only in a very abstract way. A teller machine is a device, presumably familiar to everyone, that allows you to get money from your account at any time of the day. Obviously, there are a number of constraints that such a machine must satisfy. For instance, other people should not be allowed to withdraw money from your account. Another reasonable constraint is that a user cannot overdraw more than a designated amount of money. Moreover, each transaction must be correctly reflected by the state of the user's account.

Candidate classes

ATM


  • account -- represents the customer's account in the banks database
  • atm -- performs financial services for a customer
  • cardreader -- reads and validates a customer's bankcard
  • cashdispenser -- gives cash to the customer
  • screen -- presents text and visual information
  • keypad -- the keys a customer can press
  • pin -- the authorization code
  • transaction -- performs financial services and updates the database

slide: The ATM example (1)

An initial decomposition into objects based on these requirements is shown in slide 3-atm-1. In  [WWW90], a fully detailed account is given of how one may arrive at such a decomposition by carefully reading (and re-reading) the requirements document. What we are interested in here, however, is how we may establish that we have not overlooked anything when proposing a design, and how we may verify that our design correctly reflects the requirements. This particular example nicely illustrates the need for an analysis of the use cases. To develop a proper interface, we must precisely know what a user is expected to do (for instance, insert a bank card, key in a PIN code) and how the system must respond (what messages must be displayed, how to react to a wrong PIN code, etc.). Another decision that must be made is when the account will be changed as the result of a transaction. Also, we must decide what to do when a user overdraws. A very important issue that we will look at in more detail in the next sections is how the collection of objects suggested above will interact. What means do we have to describe the cooperation between the objects, and how do we show that the proposed system meets all the requirements listed above? Moreover, can we verify that the system satisfies all the constraints mentioned in the requirements document?

Validation

However, before examining these questions and trying out different scenarios, we may as well try to eliminate the spurious classes that came up in our initial attempt. In  [Rum91], a number of reasons are summarized that may be grounds on which to reject a candidate class. See slide 3-eliminating.

Eliminating spurious classes

  • vague -- system, security-provision, record-keeping
  • attribute -- account-data, receipt, cash
  • redundant -- user
  • irrelevant -- cost
  • implementation -- transaction-log, access, communication

Good classes

  • our candidate classes

slide: Eliminating spurious classes

For example, the notion underlying the candidate class may be too vague to be represented by a class, such as the notion of system or record-keeping. Another reason for rejecting a suggested class may be that the notion represents not so much a class, but rather a possible attribute of a class. Further, a proposed class may either be redundant, for example the class user, or simply irrelevant, as is the class cost. And finally, a class may be too implementation oriented, such as the class transaction-log or classes that represent the actual communication or access to the account. Looking back, our choice of candidate classes seems to have been quite fortunate, but generally this will not be the case, and we may use the checklist above to prune the list of candidate classes.

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?

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

CRC


  • Immerse the reader in the object-ness of the material
  • Give up global knowledge of control
  • Rely on the local knowledge of objects

OO design with CRC cards

  • Class, Responsibility, Collaborators

slide: The CRC method

A number of authors have adopted this method, or developed a very similar method, for identifying objects and characterizing their functionality in an abstract way. It is doubtful, however, whether the method has any significance beyond the early stages of analysis and design. Without any more formal means to verify whether the responsibilities listed adequately characterize the intended functionality of the system, the method amounts to not much more than brainstorming. Clearly, the method needs to be complemented by more formal means to establish whether the (implicit) protocols of interaction between the objects satisfy the behavioral requirements of the system. Nevertheless, the elegant simplicity of the method is appealing, and the card format lends itself to easy incorporation in an on-line documentation system. Moreover, since the method imposes no strict order, and has relatively little overhead, it is indeed a good way to get an initial idea of what objects the system will comprise.

Example -- ATM (2)

Actually, the ATM example is an interesting example for comparing the various approaches, since it is used by many authors to illustrate their methods. In  [WWW90] the example is used for spelling out all the steps that must be taken. In  [Rum91] it is extensively described to illustrate the various modeling techniques employed by the method. And in  [BC89] the CRC cards method is illustrated by sketching the design of an automated teller machine. The approaches presented in  [BC89] and  [WWW90] are actually very closely related. Both may be characterized as responsibility-driven, in that they concentrate on responsibilities and collaboration relations to model the interaction between objects. However, the method described in  [WWW90] is much more detailed, and to some extent includes means to formally characterize the behavior of an object and its interaction with other objects. To this end it employs an informal notion of contracts as originally introduced in  [Meyer88]. In section ATM-1 a number of candidate classes have been suggested for our ATM. Now, with the use of CRC cards, we will delineate the functionality of (a number of) these classes more precisely. Also we will establish how the various object classes must collaborate to perform their duties. At the highest level of the design, we may distinguish between two groups of classes: the classes representing the banking model (comprising the class account and the class transaction), and the classes that model the interaction with the user (comprising the class card-reader and the class cash-dispenser). At a lower level, we also need a class modeling the database that provides persistent storage for the user's account and the information needed for authorization. For each of these classes we will use a CRC card to indicate their responsibilities and the classes with which they need to collaborate. The banking model, depicted in slide 3-atm-2a, consists of the classes account and transaction. The class account keeps a record of the actual balance of the account and must allow a user to deposit or withdraw money. However, for safety reasons, these operations are never carried out directly, but are performed by an intermediary transaction object.

Banking model

ATM


account transaction
keeps balance database
deposit money
withdraw money

transaction card-reader
validation cash-dispenser
performs transfer account
keeps audit info database


slide: The ATM example (2a)

The responsibilities of the transaction class may be summarized as: the validation of user requests and the execution of money transfers. The responsibility for maintaining audit information is also assigned to the transaction class. To act as required, a transaction object needs to communicate with a number of other objects. It must acquire information from both the card-reader and the database to check, for example, whether the user has entered the right PIN code. To validate a request, it must check whether the account will be overdrawn or not. To pay the requested money, it must instruct the cash-dispenser to do so. And it must contact the database to log the appropriate audit information. In contrast, an account only needs to respond to the requests it receives from a transaction. Apart from that, it must participate in committing the transaction to the bank's database. Note that the CRC method is non-specific about how the collaborations are actually realized; it is unclear which object will take the initiative. To model these aspects we will need a more precise notion of control that tells us how the potential behavior (or responsibility) of an object is activated. The second group of classes may be called interaction classes, since these are meant to communicate with entities in the outside world, outside from the perspective of the system. Also the bank's database may be considered as belonging to the outside world, since it stores the information concerning the account and the authorization of customers in a system-independent manner. See slide 3-atm-2b.

Interaction classes

ATM


cardreader event
signals insertion transaction
decodes strip

cash dispenser event
emits cash

database event
retrieves account transaction
records transaction account
authorization database


slide: The ATM example (2b)

Both the card-reader and the cash-dispenser rely on a class called event, which is needed to model the actions of the user. For example, when a user inserts a bankcard, we expect a transaction to start. For this to happen, we must presuppose an underlying system that dispatches the event to the card-reader, which in turn notifies the teller machine that a new transaction is to take place. The flow of control between a transaction object and the cash-dispenser is far more straightforward, since a transaction object only needs to issue the appropriate instruction. However, the actual interaction between the cash-dispenser and the underlying hardware, that turns out the money, may be quite intricate. The database may either respond directly to the request coming from the account or transaction object or it may respond to events by taking the initiative to call the appropriate methods of the account and transaction objects. Whether the database may be accessed directly or will only react to events is actually dependent on the control model we assume when developing the system model.

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

  • actor -- operates (suffers no operations)
  • server -- suffers operations
  • agent -- suffers and operates ( actor & server)

slide: Object roles

An important function of the design document is to elucidate the role of each object class in the system, and to point out how the objects cooperate to complete the task. In  [Booch86], a distinction is made between objects that suffer no operations (actors), objects that only suffer operations (servers) and objects that both suffer and require operations (agents). Such a characterization in terms of initiative may give a first indication of the role an object plays in the system. For example, the account class in our ATM example is best characterized as a server class, whereas the transaction class may be regarded, in the terminology of  [Booch86], as an actor class, since it actively controls the computation. In many cases, the software control model adopted will also influence the way in which individual objects are supposed to behave. See slide 3-roles. With respect to a global view of the system, it is necessary to ensure that each object class is completely defined, that is to establish that each class provides a sufficiently complete method interface. In  [Booch86], a characterization is given of the kinds of methods that may occur in an interface. These include methods to create or destroy an object, methods to modify the state of an object and methods that only provide information on the state of an object, or parts thereof. Before being able to make final decisions with respect to the functionality of a class, however, it is generally necessary to get a clear overall picture of the system first. This requires what  [Booch86] characterizes as round trip gestalt design, which in other words expresses the need to

analyze a little,

design a little,

implement a little,

test a little ... (The notion of gestalt comes from perception psychology, where it means a global perceptual configuration emerging from the background.)

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  [HOB87], the notion of types gives a natural criterion for modularization, perhaps not so much as a guideline to arrive at a particular object decomposition, but as a means to judge whether the modular structure of a system is consistently defined, that is technically well-typed.

Contractual obligations

clientsupplier
pre-conditionobligationbenefit
post-conditionbenefitobligation


slide: Contractual obligations

The meaning of a type must be understood as a formal characterization of the behavior of the elements belonging to the type. A type consists of a (possibly infinite) collection of elements which is characterized by the definition of the type. For example, a class defines such a collection, namely the instances of the class, whose behavior is constrained by the specification of the class.

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

  • require -- method call pre-condition
  • ensure, promise -- post-condition
  • invariant -- object invariance

slide: Formal specification of contracts

Intuitively, contracts have a clear analogy to our business affairs in everyday life. For instance, when buying audio equipment, as a client you wish to know what you get for the price you pay, whereas the dealer may require that you pay in cash. Following this metaphor through, we see that the supplier may actually benefit from imposing a (reasonable) pre-condition and that the client has an interest in a well-stated post-condition. Most people are not willing to pay without knowing what they will get for their money.

Language support

The use of contracts was originally proposed by  [Meyer88], and is directly supported by the language Eiffel, which offers the keywords require (to indicate a pre-condition), ensure (to indicate a post-condition) and invariant (to indicate the invariance condition). See slide 3-formal. The Eiffel environment has options to dynamically check any of the three kinds of assertions, even selectively per class. The assertions, except for the invariance condition, are directly embedded in the code. Although less elegant, the same functionality can be achieved in C++ by using the assert macro defined in {\tt assert.h} as explained in section ASSERT, which also introduced the require and promise macros for C++.

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  [Cline], which introduces an extension of C++ called Annotated C++. Instead of directly embedding assertions in the code, Annotated C++ requires the user to specify separately the axioms characterizing the functionality of the methods and their effect on the state of the object.

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.

  class account {  
account

public: account(); // assert( invariant() ); virtual float balance() { return _balance; } void deposit(float x);
to deposit money

// require( x >= 0 ); // 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; };

slide: The $account$ contract

The interface for the account class specifies in an abstract way what the user expects of an account. From the perspective of design, the behavioral abstraction expressed by the axioms is exactly what we need, in principle. The implementation must guarantee that these constraints are met.

System development

From the perspective of system development, the notion of contracts has some interesting consequences. Assertions may be used to decide who is responsible for any erroneous behavior of the system. See slide 3-limits.

System development

  • violated pre-condition -- bug in client
  • violated post-condition -- bug in supplier

A pre-condition limits the cases that a supplier must handle!


slide: System development with contracts

For example, imagine that you are using a software library to implement a system for financial transactions and that your company suffers a number of losses due to bugs in the system. How would you find out whether the loss is your own fault or whether it is caused by some bug in the library? Perhaps surprisingly, the use of assertions allows you to determine exactly whether to sue the library vendor or not. Assume that the classes in the library are all annotated with assertions that can be checked dynamically at runtime. Now, when you replay the examples that resulted in a loss for your company with the option for checking pre- and post-conditions on, it can easily be decided who is in error. In the case that a pre-condition of a method signals violation, you, as a client of a library object, are in error. However, when no pre-condition violation is signaled, but instead a post-condition is violated, then the library object as the supplier of a service is in error; and you may proceed to go to court, or do something less dramatic such as asking the software vendor to correct the bug.

Realization

The contract specified in the account class interface may actually be enforced in code as illustrated below.

  class account { 
account

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; } };

slide: The realization of the $account$ contract

The additional variable {\em old_balance} is needed to compare the state preceding an operation with the state that results afterwards. The old state must explicitly be copied by calling hold. In this respect, Eiffel offers better support than C++. Whenever balance() proves to be less than zero, the procedure sketched above can be used to determine whether the error is caused by an erroneous method invocation, for example when calling withdraw(x) with x >= balance(), or whether the implementation code contains a bug. For the developer of the software, pre-conditions offer a means to limit the number of cases that a method must be able to handle. Often, programmers tend to anticipate all possible uses. For instance, many programs or systems have options that may be learned only when inspecting the source code but are otherwise undocumented. \nop{See for example [UndocDos].} Rather than providing all possible options, for now and the future, it is more sensible to delineate in a precise manner what input will be processed and what input is considered illegal. For the developer, this may significantly reduce the effort of producing the software.

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.

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.


Refining a contract -- state responsibilities and obligations

  • invariance -- respect the invariants of the base class
  • methods -- services may be added or refined

Refining a method -- like improving a business contract


  class C : public P {
  	virtual void m();
  	}
  

  • pre(m_C) >= pre(m_P)

  • post(m_C) <= post(m_P)


slide: Contracts and inheritance

First, the invariant of the base class must apply to all instances of the derived class. In other words, the invariance assertions of the derived class must be logically equal to or stronger than the assertions characterizing the invariant properties of the base class. This requirement may be verified by checking that the invariance properties of the base class can be logically derived from the statement asserting the invariance properties of the derived class. The intuition underlying this requirement is that the behavior of the derived class is more tightly defined and hence subject to stronger invariance conditions. Secondly, each method occurring in the base class must occur in the derived class, possibly in a refined form. Note that from a type-theoretical point of view it is perfectly all right to add methods but strictly forbidden to delete methods, since deleting a method would violate the requirement of behavioral conformance that adheres to the subtype relation. Apart from adding a method, we may also refine existing methods. Refining a method involves strengthening the post-condition and weakening the pre-condition. Suppose that we have a class C derived from a base class P, to verify that the method m_C refines the method m_P defined for the base class P, we must check, assuming that the signatures of m_C and m_P are compatible, that the post-condition of m_C is not weaker than the post-condition of m_P, and also that the pre-condition of m_C is not stronger than the pre-condition of m_P. This rule may at first sight be surprising, because of the asymmetric way in which post-conditions and pre-conditions are treated. But reflecting on what it means to improve a service, the intuition underlying this rule, and in particular the contra-variant relation between the pre-conditions involved, is quite straightforward. To improve or refine a service, in our commonsense notion of a service, means that the quality of the product or the result delivered becomes better. Alternatively, a service may be considered as improved when, even with the result remaining the same, the cost of the service is decreased. In other words, a service is improved if either the client may have higher expectations of the result or the requirements on the client becomes less stringent. The or is non-exclusive. A derived class may improve a service while at the same time imposing fewer constraints on the clients.

Example

As an example of improving a contract, consider the refinement of the class account into a class {\em credit_account}, which allows a consumer to overdraw an account to a limit of some maximum amount.

  class credit_account : public account { 
credit_account

public: credit_account(float x) { _maxcredit = x; _credit = 0; } float balance() { return _balance + _credit; } float credit(float x) { require( x + _credit <= _maxcredit ); hold(); _credit += x; promise( _credit = old_credit + x ); promise( _balance = old_balance); promise( invariant() ); } void reduce(float x) { require( 0 <= x && x <= _credit ); hold(); _credit -= x; promise( _credit = old_credit - x ); promise( _balance = old_balance ); promise( invariant() ); } bool invariant() { return _credit <= _maxcredit && account::invariant(); } protected: float _maxcredit, _credit; float old_credit; void hold() { old_credit = _credit; account::hold(); } };

slide: Refining the account contract

As a first observation, we may note that the invariant of account immediately follows from the invariant of credit_account. Also, we may easily establish that the pre-condition of withdraw has (implicitly) been weakened, since we are allowed to overdraw the {\em credit_account} by the amount given by credit. Note, however, that this is implied by the virtual definition of balance(). To manage the credit given, the methods credit and reduce are supplied. This allows us to leave the methods deposit and withdraw unmodified.

Runtime consistency checking

Debugging is a hopelessly time-consuming and unrewarding activity. Unless the testing process is guided by clearly specified criteria on what to test for, testing in the sense of looking for errors must be considered as ordinary debugging, that is running the system to see what will happen. Client/server contracts, as introduced in section contracts as a method for design, do offer such guidelines in that they enable the programmer to specify precisely the restrictions characterizing the legal states of the object, as well as the conditions that must be satisfied in order for legal state transitions to occur. See slide 4-contracts.

Assertions -- side-effect free

contracts


  • require -- test on delivery
  • promise -- test during development

Object invariance -- exceptions

  • invariant -- verify when needed

Global properties -- requirements

  • interaction protocols -- formal specification

slide: Runtime consistency checking

The Eiffel language is the first (object-oriented) language in which assertions were explicitly introduced as a means to develop software and to monitor the runtime consistency of a system. Contracts as supported by Eiffel were primarily influenced by notions concerning the construction of correct programs. The unique contribution of  [Meyer88] consists of showing that these notions may be employed operationally by specifying the pragmatic meaning of pre- and post-conditions defining the behavior of methods. To use assertions operationally, however, the assertion language must be restricted to side-effect free boolean expressions in the language being used. Combined with a bottom-up approach to development, the notion of contracts gives rise to the following guidelines for testing. Post-conditions and invariance assertions should primarily be checked during development. When sufficient confidence is gained in the reliability of the object definitions, checking these assertions may be omitted in favor of efficiency. However, pre-conditions must be checked when delivering the system to ensure that the user complies with the protocol specified by the contract. When delivering the system, it is a matter of contractual agreement between the deliverer and user whether pre- and/or post-conditions will be enabled. The safest option is to enable them both, since the violation of a pre-condition may be caused by an undetected violated post-condition. In addition, the method of testing for identity transitions may be used to cover higher level invariants, involving multiple objects. To check whether the conditions with respect to complex interaction protocols are satisfied, explicit consistency checks need to be inserted by the programmer. See also section global-invariants.

Towards a formal approach

Reliability is the cornerstone of reuse. Hence, object-oriented implementation, design and analysis must first and foremost support the development of reliable software, should the original claim to promote the reuse of software ever come true. Validating software by means of testing alone is clearly insufficient. As argued in  [Backhouse], the probability of finding an error is usually too small to view testing as a reliable method of detecting the error. The fallacy of any empirical approach to validating software, which includes quantitative measurements based on software metrics, is that in the end we just have to wait and see what happens. In other words, it is useless as a design methodology.

Formal specification -- contracts

  • type specification -- local properties
  • relational specification -- structural properties, type relations
  • functional specification -- requirements

Verification -- as a design methodology

  • reasoning about program specification/code

Runtime consistency -- invariance

  • behavioral types specify test cases
  • invariants and assertions monitor consistency

slide: Formal specification and verification

Verification should be at the heart of any design method. In addition to allowing us to reason about the specification and the code, the design process should result in an architectural description of the system as well as in a proof that the system meets its requirements. Looking at the various approaches to the specification and verification of software, we can see that the notion of invariance plays a crucial role in developing provably correct solutions for a variety of problems (cf. Gries, 1981; Backhouse, 1986; Apt and Olderog, 1991; Dahl, 1992). Invariance, as we observed when discussing object test methods, also play an important role in testing the runtime consistency of a system. Hence, from a pragmatic point of view, studying formal approaches may help us become aware of the properties that determine the runtime consistency of object-oriented systems. In part III (chapter 10), we will explore what formal methods we have available for developing object-oriented software. Our starting point will be the foundations underlying the notion of contracts as introduced in  [Meyer88]. We will take a closer look at the relation between contracts and the specification of the properties of abstract data types. Also, we will look at methods allowing us to specify structural and functional relations between types, as may occur in behavioral compositions of objects. More specifically, we will study the means available to relate an abstract specification of the properties of a data type to a concrete implementation. These studies are based on an analysis of the notion of abstract data types, and the relation between inheritance and subtyping. In particular, we will look at rules to determine whether a subclass derived by inheritance conforms to the subtype relation that we may define in a formal approach to object types.

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

This chapter presented an overview of the issues involved in the design and software engineering of object-oriented software. The approach taken may be characterized as eclectic, in that various methods are referred to when illustrating design issues without commitment to a particular method or approach.

Development methods

1


  • perspectives of modeling
  • requirements engineering -- Fusion
  • methods for analysis and design -- a comparative study

slide: Section 3.1: Development methods

In section 1, we discussed perspectives of modeling and requirements engineering. We looked at the second-generation development method Fusion and made a comparative study of analysis and design methods. We then discussed the differences between functional and object-oriented development approaches.

Identifying objects

2


  • object-oriented design -- decomposition into objects
  • object model -- objects suffer and require
  • heuristics -- balance between nouns and verbs
  • evaluation -- eliminating spurious classes
  • class design -- class, responsibilities and collaborations

slide: Section 3.2: Identifying objects

In section 2, we discussed the issues that arise in defining an object model. We looked at heuristics for identifying objects, based on a linguistic analysis of the requirements document, and discussed the evaluation criteria that may be used for eliminating spurious classes. Also, the CRC method, which approaches class design by delineating responsibilities and collaborations, was illustrated with some examples.

Contracts

3


  • types -- as an organizing principle
  • contracts -- obligations and benefits
  • subtyping -- the substitutability requirement
  • partial types -- designed to have subtypes

slide: Section 3.3: Contracts

The object model resulting from an initial exploration may be formalized by employing types. In section 3, we discussed the notion of contracts as a means to characterize the behavioral aspects of types, specifying the restrictions and obligations of an object and its clients. We also looked at the requirements for subtype refinement and the refinement of contractual obligations.

Towards a formal approach

4


  • contracts -- formal specification
  • verification -- as a design methodology
  • runtime consistency -- invariance

slide: Section 3.4: Towards a formal approach

Finally, in section 4, we reflected on the possible contribution of formal methods to the software engineering of object-oriented systems, and concluded that the notion of contracts may play an invaluable role, both as a design methodology and as a means to establish the runtime consistency of a system.

Questions

  1. Describe the Fusion method. How does Fusion compare with other methods of OO analysis and design?
  2. Give an outline of the steps required in object-oriented design. What heuristics can you think of for identifying objects?
  3. What criteria may be used to eliminate spurious classes from an initial object model?
  4. Explain the methods of CRC cards. Give an example.
  5. Explain how you may characterize the behavior of an object by means of a contract.
  6. What benefits may design by contract have for system developers? And for users?
  7. Give a detailed account of the issues that arise in refining a contract.
  8. How may contracts be employed to test object behavior?
  9. Discuss how a formal approach may contribute to OO software development.

Further reading

 [Fowler97] is not only a good introduction to UML, but contains also many useful insights on the process of object-oriented development. Additionally,  [Fowler97a] may be read as a source on analysis patterns, which are reusable elements of analysis and design. For more information on Fusion, consult  [Fusion]. As earlier references on object-oriented methods, I recommend  [Booch94],  [WWW90] and  [Rum91]. Also worthwhile are  [Henderson93] and  [Champeaux93]. An overview and comparative study of design representation methods is given in  [Webster].  [Meyer97] is the ultimate reference on contracts. A more comprehensive article on design by contract is  [Meyer92].



[] readme course preface 1 2 3 4 5 6 7 8 9 10 11 12 appendix lectures resources
eliens@cs.vu.nl

draft version 0.1 (15/7/2001)