OOSE Unit 4 Nandita
OOSE Unit 4 Nandita
By
Dr. Nandita Bhanja Chaudhuri
Unit 4 Syllabus
• Object Design: Reusing Pattern Solutions
• Reuse Concepts, Reuse Activities: Selecting Design Patterns and Components,
Encapsulating Data Stores with the Bridge Pattern , Encapsulating Legacy
Components with the Adapter Pattern, Encapsulating Context with the Strategy
Pattern ,Encapsulating Platforms with the Abstract Factory Pattern, Encapsulating
Control Flow with the Command Pattern, Encapsulating Hierarchies with the
Composite Design Pattern , Heuristics for Selecting Design Patterns , Identifying
and Adjusting Application Frameworks
• Object Design: Specifying Interfaces
• Interface Specification Concepts: Class Implementor, Class Extender, and Class
User, Types, Signatures, and Visibility, Contracts: Invariants, Preconditions, and
Postconditions, Object Constraint Language, OCL Collections: Sets, Bags, and
Sequences, OCL Quantifiers: for All and exists
Object Design: Reusing Pattern Solutions
During object design, we close the gap between the application objects and the off-
the-shelf components by identifying additional solution objects and refining existing
objects. Object design includes:
• reuse, during which we identify off-the-shelf components and design patterns to
make use of existing solutions
• service specification, during which we precisely describe each class interface
• object model restructuring, during which we transform the object design model
to improve its understandability and extensibility
• object model optimization, during which we transform the object design model
to address performance criteria such as response time or memory utilization.
Activities of object design (UML activity diagram).
Reuse Concepts: Solution Objects,
Inheritance, and Design Patterns
Object design concepts related to reuse:
• Application Objects and Solution Objects
• Specification Inheritance and Implementation Inheritance
• Delegation
• The Liskov Substitution Principle
• Delegation and Inheritance in Design Patterns
Application Objects and Solution Objects
• Application objects, also called “domain objects,” represent concepts of the
domain that are relevant to the system.
• Solution objects represent components that do not have a counterpart in the
application domain, such as persistent data stores, user interface objects, or
middleware.
• During analysis, we identify entity objects and their relationships, attributes, and
operations. Most entity objects are application objects that are independent of
any specific system.
• During analysis, we also identify solution objects that are visible to the user, such
as boundary and control objects representing forms and transactions defined by
the system.
• During system design, we identify more solution objects in terms of software and
hardware platforms.
• During object design, we refine and detail both application and solution objects
and identify additional solution objects needed to bridge the object design gap.
Specification Inheritance and Implementation
Inheritance
• During analysis, we use inheritance to classify objects into taxonomies. This
allows us to differentiate the common behavior of the general case, that is, the
superclass (also called the “base class”), from the behavior that is specific to
specialized objects, that is, the subclasses (also called the “derived classes”).
• The focus of generalization (i.e., identifying a common superclass from a number
of existing classes) and specialization (i.e., identifying new subclasses given an
existing superclass) is to organize analysis objects into an understandable
hierarchy.
• The focus of inheritance during object design is to reduce redundancy and enhance
extensibility. By factoring all redundant behavior into a single superclass, we reduce the
risk of introducing inconsistencies during changes (e.g., when repairing a defect) since we
have to make changes only once for all subclasses. By providing abstract classes and
interfaces that are used by the application, we can write new specialized behavior by
writing new subclasses that comply with the abstract interfaces. For example, we can
write an application manipulating images in terms of an abstract Image class, which
defines all the operations that all Images should support, and a series of specialized
classes for each image format supported by the application (e.g., GIFImage, JPEGImage).
When we need to extend the application to a new format, we only need to add a new
specialized class.
• Consider the following example: Assume for a moment that Java does not provide a set
abstraction and that we needed to write our own. We decide to reuse the
java.util.Hashtable class to implement a set abstraction that we call MySet. Inserting an
element in MySet is equivalent to checking if the corresponding key exists in the table
and creating an entry if necessary. Checking if an element is in MySet is equivalent to
checking if an entry is associated with the corresponding key (see Figure 8-3, left
column).
• Such an implementation of a set allows us to reuse code and provides us with the
desired behavior. It also provides us, however, with unwanted behavior. For
example, Hashtable implements the containsKey() operation to check if the
specified object exists as a key in the Hashtable and the containsValue() operation
to check if the specified object exists as an entry. containsKey() is inherited by
MySet, but containsValue() is overwritten. Given our implementation, the
operation containsValue() invoked on a MySet object has the same behavior as
containsKey(), which is counterintuitive. Worse, a developer could use both
containsKey() and containsValue(), which would make it difficult to change the
internal representation of MySet in the future. For example, if we decided to
implement MySet as a List instead of a Hashtable, all invocations to
containsKey() would become invalid. To address this issue, we could overwrite all
operations inherited from Hashtable that should not be used on MySet with
methods throwing exceptions. However, this would lead to a MySet class that is
difficult to understand and reuse.
• Inheritance yields its benefits by decoupling the classes using a superclass from
the specialized subclasses. In doing so, however, it introduces a strong coupling
along the inheritance hierarchy between the superclass and the subclass.
Whereas this is acceptable when the inheritance hierarchy represents a taxonomy
(e.g., it is acceptable for Image and GIFImage to be tightly coupled), it introduces
unwanted coupling in the other cases. In our example, two previously unrelated
concepts, Hashtable and Set, become tightly coupled as a result of subclassing,
introducing many issues when Hashtable is modified or when a Set is used by a
class as a specialized Hashtable. The fundamental problem in this example is that,
although Hashtable provides behavior that we would like to reuse in implementing
Set, because that would save us time, there is no taxonomy in which the Set
concept is related to the Hashtable concept.
Figure 8-3 An example of implementation inheritance. The left column depicts a questionable implementation of MySet
using implementation inheritance. The right column depicts an improved implementation using delegation (UML class
diagram and Java).
• The use of inheritance for the sole purpose of reusing code is called
implementation inheritance. With implementation inheritance, developers reuse
code quickly by subclassing an existing class and refining its behavior. A Set
implemented by inheriting from a Hashtable is an example of implementation
inheritance. Conversely, the classification of concepts into type hierarchies is
called specification inheritance (also called “interface inheritance”). The UML
class model of Figure 8-4 summarizes the four different types of inheritance we
discussed in this section
Fig. 8-4
Delegation
• Delegation is the alternative to implementation inheritance that should be used
when reuse is desired. A class is said to delegate to another class if it
implements an operation by resending a message to another class. Delegation
makes explicit the dependencies between the reused class and the new class. The
right column of Figure 8-3 shows an implementation of MySet using delegation
instead of implementation inheritance. The only significant change is the private
field table and its initialization in the MySet() constructor. This addresses both
problems we mentioned before:
1) Extensibility. The MySet on the right column does not include the
containsKey() method in its interface and the new field table is private. Hence, we
can change the internal representation of MySet to another class (e.g., a List)
without impacting any clients of MySet.
2) Subtyping. MySet does not inherit from Hashtable and, hence, cannot be
substituted for a Hashtable in any of the client code. Consequently, any code
previously using Hashtables still behaves the same way.
Figure 8-12 Applying the Abstract Factory design pattern to different intelligent house platforms (UML class diagram,
dependencies represent «call» relationships).
Inheritance and delegation in the Abstract Factory pattern
• The Abstract Factory pattern uses specification inheritance to decouple the
interface of a product from its realization. However, since products of the same
platform usually depend on each other and access the concrete product classes,
products of different platforms cannot be substituted transparently. For example,
EIBBulbs are incompatible with LuxmateBulbs and should not be mixed within
the same intelligent house system. To ensure that a consistent set of products is
created, the Client can only create products by using a ConcreteFactory, which
delegates the creation operations to the respective products. By using specification
inheritance to decouple ConcreteFactories from their interface, product families
from different manufacturers can be substituted transparently from the client.
Encapsulating Control Flow with the Command
Pattern
• In interactive systems and in transaction systems, it is often desirable to
execute, undo, or store user requests without knowing the content of the
request. For example, consider the case of matches in the ARENA tournament
management system. We want to record individual moves in matches so that these
moves can be replayed by a spectator at a later date. However, we also want
ARENA to support a broad spectrum of games, so we do not want the classes
responsible for recording and replaying moves to depend on any specific game.
• We can apply the Command design pattern to this effect. The key to decoupling
game moves from their handling is to represent game moves as command objects
that inherit from an abstract class called Move in Figure 8-13. The Move class
declares operations for executing, undoing, and storing commands, whereas
ConcreteCommands classes (i.e., TicTacToeMove and ChessMove in ARENA)
implement specific commands. The classes responsible for recording and
replaying games only access the GameMove abstract class interface, thus making
the system extensible to new Games.
Figure 8-13 Applying the Command design pattern to Matches in ARENA (UML class diagram).
Inheritance and delegation in the Command pattern
• The Command design pattern uses specification inheritance between the
Command class and ConcreteCommands, enabling new commands to be added
independently from the Invoker. Delegation is used between ConcreteCommands
and Receivers, and between Invoker and Command, enabling ConcreteCommands
to be dynamically created, executed, and stored. The Command pattern is often
used in a Model/View/Controller software architecture, where Receivers are
model objects, Invoker and Commands are controller objects, and Clients creating
Commands are view objects.
Encapsulating Hierarchies with the Composite
Design Pattern
• User interface toolkits, such as Swing and Cocoa, provide the application
developer with a range of classes as building blocks. Each class implements a
specialized behavior, such as inputting text, selecting and deselecting a check box,
pushing a button, or pulling down a menu. The user interface design can aggregate
these components into Windows to build application-specific interfaces. For
example, a preferences dialog may include a number of on-off check boxes for
enabling different features in the application.
• As windows become more complex and include many different user interface
objects, their layout (i.e., moving and resizing each component so that the window
forms a coherent whole) becomes increasingly unmanageable. Consequently,
modern toolkits enable the developer to organize the user interface objects into
hierarchies of aggregate nodes, called “panels,” that can be manipulated the same
way as the concrete user interface objects. For example, our preferences dialog
can include a top panel for the title of the dialog and instructions for the user, a
center panel containing the checkboxes and their labels, and a bottom panel for the
‘ok’ and ‘cancel’ button. Each panel is responsible for the layout of its subpanels,
called “children,” and the overall dialog only has to deal with the three panels
(Figures 8-14 and 8-15).
Figure 8-15 UML object diagram for the user interface objects of Figure 8-14.
• Swing addresses this problem with the Composite design pattern as depicted in
Figure 8-16. An abstract class called Component is the roof of all user
interface objects, including Checkboxes, Buttons, and Labels. Composite, also a
subclass of Component, is a special user interface object representing aggregates
including the Panels we mentioned above. Note that Windows and Applets (the
root of the instance hierarchy) are also Composite classes that have additional
behavior for dealing with the window manager and the browser, respectively.
Figure 8-16 Applying the Composite design pattern to user interface widgets (UML class diagram). The Swing Component
hierarchy is a Composite in which leaf widgets (e.g., Checkbox, Button, Label) specialize the Component interface, and
aggregates (e.g., Panel, Window) specialize the Composite abstract class. Moving or resizing a Composite impacts all of its
children.
Heuristics for Selecting Design Patterns
• Identifying the correct design pattern for a given problem is difficult unless you
already have some experience in using design patterns. Pattern catalogs are large
and varied, and one cannot expect developers to read them completely. As design
patterns address a specific design goal or a specific nonfunctional requirement,
another technique is to use key phrases in the Requirements Analysis Document
(RAD) and the System Design Document (SDD) to select candidate patterns.
This is similar to the Abbott’s natural language technique.
Natural language heuristics for selecting design patterns
Identifying and Adjusting Application
Frameworks
• Application frameworks
• An application framework is a reusable partial application that can be
specialized to produce custom applications. In contrast to class libraries,
frameworks are targeted to particular technologies, such as data processing or
cellular communications, or to application domains, such as user interfaces or
real-time avionics. The key benefits of application frameworks are reusability and
extensibility. Framework reusability leverages the application domain knowledge
and the prior effort of experienced developers to avoid recreation and
revalidation of recurring solutions. An application framework enhances
extensibility by providing hook methods, which are overwritten by the application
to extend the application framework. Hook methods systematically decouple the
interfaces and behaviors of an application domain from the variations required by
an application in a particular context. Framework extensibility is essential to
ensure timely customization of new application services and features.
Frameworks can be classified by their position in the software development process.
• Infrastructure frameworks aim to simplify the software development process.
Examples include frameworks for operating systems, debuggers,
communication tasks, user interface design, and Java Swing. System
infrastructure frameworks are used internally within a software project and are
usually not delivered to a client.
• Middleware frameworks are used to integrate existing distributed applications
and components. Common examples include Microsoft’s MFC and DCOM, Java
RMI, WebObjects, WebSphere [IBM], WebLogic Enterprise Application [BEA],
implementations of CORBA, and transactional databases.
• Enterprise application frameworks are application specific and focus on
domains such as telecommunications, avionics, environmental modeling,
manufacturing, financial engineering, and enterprise business activities.
Frameworks can also be classified by the techniques used to extend them.
• Whitebox frameworks rely on inheritance and dynamic binding for
extensibility. Existing functionality is extended by subclassing framework base
classes and overriding predefined hook methods using patterns such as the
template method pattern.
• Blackbox frameworks support extensibility by defining interfaces for
components that can be plugged into the framework. Existing functionality is
reused by defining components that conform to a particular interface and
integrating these components with the framework using delegation.
• Whitebox frameworks require intimate knowledge of the framework’s internal
structure. Whitebox frameworks produce systems that are tightly coupled to the
specific details of the framework’s inheritance hierarchies, and thus changes in the
framework can require the recompilation of the application. Blackbox frameworks
are easier to use than whitebox frameworks because they rely on delegation
instead of inheritance. However, blackbox frameworks are more difficult to
develop because they require the definition of interfaces and hooks that anticipate
a wide range of potential use cases. Moreover, it is easier to extend and
reconfigure blackbox frameworks dynamically, as they emphasize dynamic object
relationships rather than static class relationships.
Frameworks, class libraries, and design patterns
Frameworks are closely related to design patterns, class libraries, and components.
1. Design patterns versus frameworks. The main difference between frameworks
and patterns is that frameworks focus on reuse of concrete designs, algorithms,
and implementations in a particular programming language. In contrast,
patterns focus on reuse of abstract designs and small collections of
cooperating classes. Frameworks focus on a particular application domain,
whereas design patterns can be viewed more as building blocks of
frameworks.
2. Class libraries versus frameworks. Classes in a framework cooperate to
provide a reusable architectural skeleton for a family of related applications. In
contrast, class libraries are less domain specific and provide a smaller scope
of reuse. For instance, class library components, such as classes for strings,
complex numbers, arrays, and bitsets, can be used across many application
domains.
• Class libraries are typically passive; that is, they do not implement or constrain the
control flow. Frameworks, however, are active; that is, they control the flow of
control within an application. In practice, developers often use frameworks and
class libraries in the same system. For instance, frameworks use class libraries,
such as foundation classes, internally to simplify the development of the
framework. Similarly, application-specific code invoked by framework event
handlers uses class libraries to perform basic tasks, such as string processing, file
management, and numerical analysis.
3. Components versus frameworks. Components are self-contained instances of
classes that are plugged together to form complete applications. In terms of
reuse, a component is a blackbox that defines a cohesive set of operations that can
be used solely with knowledge of the syntax and semantics of its interface.
Compared with frameworks, components are less tightly coupled and can even be
reused on the binary code level. That is, applications can reuse components without
having to subclass from existing base classes.
• The advantage is that applications do not always have to be recompiled when components
change. The relationship between frameworks and components is not predetermined. On
the one hand, frameworks can be used to develop components, where the component
interface provides a facade pattern for the internal class structure of the framework. On
the other hand, components can be plugged into blackbox frameworks. In general,
frameworks are used to simplify the development of infrastructure and middleware
software, whereas components are used to simplify the development of end-user
application software.
• A framework example
• WebObjects is a set of frameworks for developing interactive Web applications by
accessing existing data from relational databases. WebObjects consists of two
infrastructure frameworks. The WebObjects framework handles the interaction
between Web browsers and Web servers. The Enterprise Object Framework (EOF)
handles the interaction between Web servers and relational databases. The EOF supports
database adapters that allow applications to connect to database management systems
from particular vendors. For example, the EOF provides database adapters for Informix,
Oracle, and Sybase servers and ODBC-compliant adapters. In the following discussion,
we concentrate on the WebObjects framework. More information on the EOF can be
found in [Wilson & Ostrem, 1999].
Figure 8-17 An example of dynamic site with WebObjects (UML component diagram).
• Figure 8-17 shows an example of a dynamic publishing site built with
WebObjects. The WebBrowser originates an HTTP request containing a URL,
which is sent to the WebServer. If the WebServer detects that the request is to a
static HTML page, it passes it on the StaticHTML object, which selects and sends
the page back to the WebBrowser as a response. The WebBrowser then renders it
for the user. If the WebServer detects that the request requires a dynamic HTML
page, it passes the request to a WebObjects WOAdaptor. The WOAdaptor
packages the incoming HTML request and forwards it to the
WebObjectsApplication object. Based on Templates defined by the developer and
relevant data retrieved from the RelationalDatabase, the WebObjectsApplication
then generates an HTML response page, which is passed back through the
WOAdaptor to the WebServer. The WebServer then sends the page to the
WebBrowser, which renders it for the user.
• A key abstraction provided by the WebObjects framework is an extension of the
HTTP protocol to manage state. HTTP is a stateless request-response protocol;
that is, a response is formulated for each request, but no state is maintained
between successive requests. In many Web-based applications, however, state
must be kept between requests. For example in ARENA, Players should not
identify themselves for each move they play. Moreover, Players must be able to
continue playing in the same Match even if their WebBrowser is restarted. Several
techniques have been proposed to keep track of state information in Web
applications, including dynamically generated URLs, cookies, and hidden HTML
fields. WebObjects provides the classes shown in Figure 8-18 to achieve the same
purpose.
Figure 8-18 WebObject’s State Management Classes. The HTTP protocol is inherently stateless. The State Management
Classes enable information between individual requests to be maintained.
• The WOApplication class represents the application running on the WebServer
waiting for requests from the associated WebBrowser. A cycle of the request-
response loop begins whenever the WOAdaptor receives an incoming HTTP
request. The WOAdaptor packages this request in a WORequest object and
forwards it to the application object of class WOApplication. Requests are always
triggered by a URL submitted by the WebBrowser. The class WOSession
encapsulates the state of an individual session, allowing it to track different users,
even within a single application. A WOSession consists of one or more
WOComponents, which represent a reusable Web page or portion of a Web page
for display within an individual session. WOComponents may contain
DynamicElements. When an application accesses the database, one or more of the
DynamicElements of a component are filled with information retrieved from the
database. The WOSessionStore provides persistency for WOSession objects: it
stores sessions in the server and restores them by the application upon request.
• The essence of building a WebObjects application is to refine the classes
WOApplication, WOSession, and WOComponent and to intercept the flow of
requests sent and received between them. Inherited methods from these classes are
overridden when the developer needs to extend the default behavior. The earliest
control point for refining objects of type WOApplication is when they are
constructed. The last point of control is when the application object terminates. By
adding code to the application object constructor or overriding the WOApplication
terminate() method, the developer can customize the behavior of the WebObjects
application as desired.
Object Design: Specifying Interfaces
• During object design, we identify and refine solution objects to realize the
subsystems defined during system design.
• During this activity, our understanding of each object deepens: we specify the type
signatures and the visibility of each of the operations, and, finally, we describe the
conditions under which an operation can be invoked and those under which the
operation raises an exception.
The interface specification activities of object design include:
• identifying missing attributes and operations
• specifying type signatures and visibility
• specifying invariants
• specifying preconditions and postconditions.
An Overview of Interface Specification
• The analysis object model describes the entity, boundary, and control objects that
are visible to the user. The analysis object model includes attributes and operations
for each object.
• Subsystem decomposition describes how these objects are partitioned into
cohesive pieces that are realized by different teams of developers. Each subsystem
includes highlevel service descriptions that indicate which functionality it provides
to the others.
• Hardware/software mapping identifies the components that make up the virtual
machine on which we build solution objects. This may include classes and APIs
defined by existing components.
• Boundary use cases describe, from the user’s point of view, administrative and
exceptional cases that the system handles.
• Design patterns selected during object design reuse describe partial object design
models addressing specific design issues.
To this end, interface specification includes the following activities:
• Identify missing attributes and operations. During this activity, we examine
each subsystem service and each analysis object. We identify missing operations
and attributes that are needed to realize the subsystem service. We refine the
current object design model and augment it with these operations.
• Specify visibility and signatures. During this activity, we decide which
operations are available to other objects and subsystems, and which are used only
within a subsystem. We also specify the return type of each operation as well as
the number and type of its parameters. This goal of this activity is to reduce
coupling among subsystems and provide a small and simple interface that can be
understood easily by a single developer.
• Specify contracts. During this activity, we describe in terms of constraints the
behavior of the operations provided by each object. In particular, for each
operation, we describe the conditions that must be met before the operation is
invoked and a specification of the result after the operation returns.
Interface Specification Concepts
• Class Implementor, Class Extender, and Class User
• Types, Signatures, and Visibility
• Contracts: Invariants, Preconditions, and Postconditions
• Object Constraint Language
• OCL Collections: Sets, Bags, and Sequences
• OCL Qualifiers: forAll and exists
Class Implementor, Class Extender, and Class
User
So far, we have treated all developers as equal. Now that we are delving into the details of
object design and implementation, we need to differentiate developers based on their point
of view. While all use the interface specification to communicate about the class of interest,
they view the specifications from radically different point of views (see also Figure 9-1):
• The class implementor is responsible for realizing the class under consideration. Class
implementors design the internal data structures and implement the code for each
public operation. For them, the interface specification is a work assignment.
• The class user invokes the operations provided by the class under consideration during
the realization of another class, called the client class. For class users, the interface
specification discloses the boundary of the class in terms of the services it provides and
the assumptions it makes about the client class.
• The class extender develops specializations of the class under consideration. Like
class implementors, class extenders may invoke operations provided by the class
of interest, the class extenders focus on specialized versions of the same services.
For them, the interface specification both a specifies the current behavior of the
class and any constraints on the services provided by the specialized class.
Figure 9-1 The Class Implementor, the Class Extender, and the Class User role (UML use case diagram).
• For example, consider the ARENA Game abstract class (Figure 9-2). The
developer responsible for realizing the Game class, including operations that apply
to all Games, is a class implementor. The League and Tournament classes invoke
operations provided by the Game interface to organize and start Matches.
Developers responsible for League and Tournament are class users of Game. The
TicTacToe and Chess classes are concrete Games that provide specialized
extensions to the Game class. Developers responsible for those classes are class
extenders of Game.
Class User
Class
Extender
Figure 9-2 ARENA Game abstract class with user classes and extender classes.
Types, Signatures, and Visibility
• During analysis, we identified attributes and operations without necessarily
specifying their types or their parameters. During object design, we refine the
analysis and system design models by completing type and visibility information.
The type of an attribute specifies the range of values the attribute can take
and the operations that can be applied to the attribute. For example, consider
the attribute maxNumPlayers of the Tournament class in ARENA (Figure 9-3).
maxNumPlayers represent the maximum number of Players who can be accepted
in a given Tournament. Its type is int, denoting that it is an integer number. The
type of the maxNumPlayers attribute also defines the operations that can be
applied to this attribute: we can compare, add, subtract, or multiply other integers
to maxNumPlayers.
• Operation parameters and return values are typed in the same way as attributes
are. The type constrains the range of values the parameter or the return value can
take. Given an operation, the tuple made out of the types of its parameters
and the type of the return value is called the signature of the operation. For
example, the acceptPlayer() operation of Tournament takes one parameter of type
Player and does not have a return value. The signature for acceptPlayer() is then
acceptPlayer(Player):void. Similarly, the getMaxNumPlayers() operation of
Tournament takes no parameters and returns an int. The signature of
getMaxNumPlayers() is then getMaxNumPlayers(void):int.
• The class implementor, the class user, and the class extender all access the
operations and attributes of the class under consideration. However, these
developers have different needs and are usually not allowed to access all
operations of the class. For example, a class implementor accesses the internal
data structures of the class that the class user cannot see. The class extender
accesses only selected internal structures of superclasses. The visibility of an
attribute or an operation is a mechanism for specifying whether the attribute or
operation can be used by other classes or not. UML defines four levels of
visibility:
• A private attribute can be accessed only by the class in which it is defined.
Similarly, a private operation can be invoked only by the class in which it is
defined. Private attributes and operations cannot be accessed by subclasses or
calling classes. Private operations and attributes are intended for the class
implementor only.
• A protected attribute or operation can be accessed by the class in which it is
defined and by any descendant of that class. Protected operations and attributes
cannot be accessed by any other class. Protected operations and attributes are
intended for the class extender.
• A public attribute or operation can be accessed by any class. The set of public
operations and attributes constitute the public interface of the class and is intended
for the class user.
• An attribute or an operation with visibility package can be accessed by any class
in the nearest enclosing package. This visibility enables a set of related classes (for
example, forming a subsystem) to share a set of attributes or operations without
having to make them public to the entire system.
• An attribute or an operation with visibility package can be accessed by any class
in the nearest enclosing package. This visibility enables a set of related classes (for
example, forming a subsystem) to share a set of attributes or operations without
having to make them public to the entire system.
Visibility is denoted in UML by prefixing the name of the attribute or the operation
with a character symbol: – for private, # for protected, + for public, or ~ for
package. For example, in Figure 9-3, we specify that the maxNumPlayers attribute
of Tournament is private, whereas all the class operations are public.
Figure 9-3 Declaration for the Tournament class (UML class model and Java excerpts).
Contracts: Invariants, Preconditions, and
Postconditions
Contracts are constraints on a class that enable class users, implementors, and extenders
to share the same assumptions about the class. A contract specifies constraints that the class
user must meet before using the class as well as constraints that are ensured by the class
implementor and the class extender when used. Contracts include three types of constraints:
• An invariant is a predicate that is always true for all instances of a class. Invariants are
constraints associated with classes or interfaces. Invariants are used to specify consistency
constraints among class attributes.
• A precondition is a predicate that must be true before an operation is invoked.
Preconditions are associated with a specific operation. Preconditions are used to specify
constraints that a class user must meet before calling the operation.
• A postcondition is a predicate that must be true after an operation is invoked.
Postconditions are associated with a specific operation. Postconditions are used to specify
constraints that the class implementor and the class extender must ensure after the
invocation of the operation.
• For example, consider the Java interface for the Tournament from Figure 9-3. This class
provides an acceptPlayer() method to add a Player in the Tournament, a removePlayer()
method to withdraw a Player from the Tournament (e.g., because the player cancelled his
application), and a getMaxNumPlayers() method to get the maximum number of Players
who can participate in this Tournament.
• An example of an invariant for the Tournament class is that the maximum number
of Players in the Tournament should be positive. If a Tournament is created with a
maxNumPlayers that is zero, the acceptPlayer() method will always violate its contract
and the Tournament will never start. Using a boolean expression, in which t is a
Tournament, we can express this invariant as
t.getMaxNumPlayers() > 0
• An example of a precondition for the acceptPlayer() method is that the Player to
be added has not yet already been accepted in the Tournament and that the
Tournament has not yet reached its maximum number of Players. Using a boolean
expression, in which t is a Tournament and p is a Player, we express this as
!t.isPlayerAccepted(p) and t.getNumPlayers() < t.getMaxNumPlayers()
t.getNumPlayers_afterAccept = t.getNumPlayers_beforeAccept + 1
• where numPlayers_afterAccept and numPlayers_afterAccept are the current and
number of Players before and after acceptPlayer(), respectively.
• The context keyword indicates the entity to which the expression applies. This is
followed by one of the keywords inv, pre, and post, which correspond to the UML
stereotypes «invariant», «precondition», and «postcondition», respectively. Then
follows the actual OCL expression. OCL’s syntax is similar to object-oriented
languages such as C++ or Java. However, OCL is not a procedural language and
thus cannot be used to denote control flow. Operations can be used in OCL
expressions only if they do not have any side effects.
• For invariants, the context for the expression is the class associated with the
invariant. The keyword self (e.g., self.numElements) denotes all instances of the
class. Attributes and operations are accessed using the dot notation (e.g.,
self.maxNumPlayers accesses maxNumPlayers in the current context). The self
keyword can be omitted if there is no ambiguity.
• For preconditions and postconditions, the context of the OCL expression is an
operation. The parameters passed to the operation can be used as variables in the
expression. For example, consider the following precondition on the
acceptPlayer() operation in Tournament:
• The variable p in the constraint !isPlayerAccepted(p) refers to the parameter p
passed to the acceptPlayer(p) operation. As this is a precondition, the constraint
must be True before the execution of the acceptPlayer(p) operation. Hence, the
constraint reads in English: “acceptPlayer(p) assumes that p has not yet been
accepted in the Tournament”. We can write several preconditions for the same
operation. If there are more than one precondition for a given operation, all
preconditions must be True before the operation can be invoked. For example, we
can also state that the Tournament must not yet have reached the maximum
number of Players before invoking acceptPlayer():
• Post conditions are written in the same way as preconditions, except for the
keyword post indicating that the constraint is evaluated after the operation returns.
For example, the following postcondition on acceptPlayer(p) states that the Player
p should be known to the Tournament after acceptPlayer() returns:
• For postconditions, we often need to refer to the value of an attribute before and
after the execution of the operation. For this purpose, the suffix @pre denotes the
value of self or an attribute before the execution of the operation. For example, if
we want to state that the number of Players in the Tournament increases by one
with the invocation of acceptPlayer(), we need to refer to the value of
getNumPlayers() before and after the invocation of acceptPlayer(). We can write
the following postcondition:
• @pre.getNumPlayers() denotes the value returned by getNumPlayers() before
invoking acceptPlayer(), and getNumPlayers() denotes the value returned by the
same operation after invoking acceptPlayer(). Similar to preconditions, if there is
more than one postcondition for a given operation, all postconditions must be
satisfied after the operation completes. We can therefore write the contract for the
removePlayer() operation with the same approach:
Figure 9-5 Method declarations for the Tournament class
annotated with preconditions, postconditions, and invariants
(Java, constraints using Javadoc style tags).
OCL Collections: Sets, Bags, and Sequences
• In general, constraints involve an arbitrary number of classes and attributes.
Consider the class model of Figure 9-6 representing the associations among the
League, Tournament, and Player classes. Let’s assume we want to refine the
model with the following constraints:
In the second constraint, however, the expression league.players can actually refer to
many objects, since the players association is a many-to-many association. To deal
with this situation, OCL provides additional data types called collections. There are
three types of collections:
• OCL sets are used when navigating a single association. For example,
navigating the players association of the winter:Tournament yields the set
{alice, bob}. Navigating the players association from the tttExpert:League yields
the set {alice, bob, marc, joe}. Note, however, that navigating an association of
multiplicity 1 yields directly an object, not a set. For example, navigating the
league association from winter:Tournament yields tttExpert:League (as opposed to
{tttExpert:League}).
• OCL sequences are used when navigating a single ordered association. For
example, the association between League and Tournament is ordered. Hence,
navigating the tournaments association from tttExpert:League yields
[winter:Tournament, xmas:Tournament] with the index of winter:Tournament
and xmas:Tournament being 1 and 2, respectively.
• OCL bags are multisets: they can contain the same object multiple times. Bags
are used to accumulate the objects when accessing indirectly related objects. For
example, when determining which Players are active in the tttExpert:League,
we first navigate the tournaments association of tttExpert, then the players
association from winter:Tournament, and finally the players association from
xmas:Tournament, yielding the bag {alice, bob, bob, marc, joe}. The bag
resulting from navigating the same associations from chessNovice:League results
in the empty bag, as there are no Tournaments in the chessLeague. In cases where
the number of occurrences of each object in the bag is undesired, the bag can be
converted to a set.
OCL provides many operations for accessing collections. The most often used are:
• size, which returns the number of elements in the collection
• includes(object), which returns True if object is in the collection
• select(expression), which returns a collection that contains only the elements of
the original collection for which expression is True
• union(collection), which returns a collection containing elements from both
the original collection and the collection specified as parameter
• intersection(collection), which returns a collection that contains only the
elements that are part of both the original collection and the collection
specified as parameter
• asSet(collection), which returns a set containing each element of the collection.
OCL Quantifiers: forAll and exists
So far, we presented examples of constraints using common OCL collection
operations such as includes, union, or asSet. Two additional operations on
collections enable us to iterate over collections and test expressions on each
element:
• forAll(variable|expression) is True if expression is True for all elements in the
collection.
• exists(variable|expression) is True if there exists at least one element in the
collection for which expression is True.
• For example, to ensure that all Matches in a Tournament occur within the
Tournament’s time frame, we can repetitively test the start dates of all matches
against the Tournament using forAll(). Consequently, we write this constraint as
follows:
• The OCL exists() operation is similar to forAll(), except that the expressions
evaluated on each element are ORed, that is, only one element needs to satisfy the
expression for the exists() operation to return True. For example, to ensure that
each Tournament conducts at least one Match on the first day of the Tournament,
we can write: