0% found this document useful (0 votes)
9 views111 pages

OOSE Unit 4 Nandita

The document outlines the syllabus for a course on Object-Oriented Software Engineering (OOSE) focusing on object design, reuse concepts, and design patterns. It discusses the importance of reusing design patterns, encapsulating data stores, and specifying interfaces to enhance software extensibility and performance. Key concepts include delegation versus inheritance, the Liskov Substitution Principle, and the use of design patterns like the Adapter pattern to manage complexity and facilitate modifications in software development.

Uploaded by

ppamarth2
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views111 pages

OOSE Unit 4 Nandita

The document outlines the syllabus for a course on Object-Oriented Software Engineering (OOSE) focusing on object design, reuse concepts, and design patterns. It discusses the importance of reusing design patterns, encapsulating data stores, and specifying interfaces to enhance software extensibility and performance. Key concepts include delegation versus inheritance, the Liskov Substitution Principle, and the use of design patterns like the Adapter pattern to manage complexity and facilitate modifications in software development.

Uploaded by

ppamarth2
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 111

GITAM School of Technology

19EAI334: OOSE Based Application


Development

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.

• Delegation is a preferable mechanism to implementation inheritance as it does not


interfere with existing components and leads to more robust code. Note that
specification inheritance is preferable to delegation in subtyping situations as
it leads to a more extensible design.
The Liskov Substitution Principle
• The Liskov Substitution Principle [Liskov, 1988] provides a formal definition for
specification inheritance. It essentially states that, if a client code uses the
methods provided by a superclass, then developers should be able to add new
subclasses without having to change the client code. For example, in the left
column of Figure 8-3, this means that, if a client uses a Hashtable, the client
should not have to be modified when we substitute the Hashtable for any of its
subclasses, for example MySet. Clearly, this is not the case, so the relationship
between MySet and Hashtable is not a specification inheritance relationship.
Below is the formal definition of the Liskov Substitution Principle:
Delegation and Inheritance in Design Patterns
• In general, when to use delegation or inheritance is not always clear and requires
some experience and judgement on the part of the developer. Inheritance and
delegation, used in different combinations, can solve a wide range of problems:
decoupling abstract interfaces from their implementation, wrapping around
legacy code, and/or decoupling classes that specify a policy from classes that
provide mechanism.
• In object-oriented development, design patterns are template solutions that
developers have refined over time to solve a range of recurring problems. A design
pattern has four elements:
1. A name that uniquely identifies the pattern from other patterns.
2. A problem description that describes the situations in which the pattern can be
used. Problems addressed by design patterns are usually the realization of
modifiability and extensibility design goals and nonfunctional requirements.
3. A solution stated as a set of collaborating classes and interfaces.
4. A set of consequences that describes the trade-offs and alternatives to be
considered with respect to the design goals being addressed.
• For example, we can restate the problem of writing a set class of Figure 8-3 as
implementing a new class (i.e., MySet) that complies with an existing interface
(i.e., the Java Set interface) reusing the behavior provided by an existing class
(i.e., the Hashtable class). Both the Set interface and the Hashtable class are
already provided and neither can be modified. The Adapter design pattern (Figure
8-5; Appendix A.2) is a template solution for such problems.
Figure 8-5 An example of design pattern, Adapter (adapted from [Gamma et al., 1994]).
• The Adapter pattern works as follows: An Adapter class implements each method
declared in the ClientInterface in terms of requests to the LegacyClass. Any
conversion of data structures or adjustment of behaviors is done in the Adapter
class so that Adapter behaves as expected by the Client. The Adapter pattern
enables reuse since neither the ClientInterface nor the LegacyClass need to be
modified. The Adapter pattern also encourages extensibility, as the same Adapter
class can be used for any subtypes of the LegacyClass, as subtypes can be
substituted for their supertype, according to the Liskov Substitution Principle. By
applying the Adapter pattern to our Set problem (Figure 8-6), we end up with the
same delegation relationship between MySet and Hashtable as in Figure 8-3.
• Note that the Adapter pattern uses both inheritance and delegation. When studying
design patterns, you will notice that many patterns use a mix of inheritance and
delegation and therefore look similar. However, the same mechanisms are used in
subtly different ways. To clarify the differences, we use the following terms to
denote different classes participating in the pattern:
• The client class accesses the pattern. In the class diagram of the Adapter pattern
(Figure 8-5), this class is simply called Client. Client classes can be either existing
classes of a class library or new classes of the system under development.
• The pattern interface is the part of the pattern that is visible to the client class.
Often, the pattern interface is realized by an abstract class or an interface. In the
Adapter pattern, this class is called ClientInterface.
• The implementor class provides the lower-level behavior of the pattern. In the
Adapter pattern, the LegacyClass and the Adapter are implementor classes. In
many patterns, a number of collaborating implementor classes are needed to
realize the pattern behavior.
• The extender class specializes an implementor class to provide a different
implementation or an extended behavior of the pattern. In the Adapter pattern, the
subtypes of LegacyClass are extender classes. Note that, often, extender classes
represent future classes that developers anticipate
Reuse Activities: Selecting Design Patterns and
Components
• On the one hand, during system design, we construct solid walls between
subsystems to manage complexity by breaking the system into smaller pieces
and to prevent changes in one subsystem from affecting other subsystems.
• On the other hand, during object design, we want the software to be modifiable
and extensible to minimize the cost of future changes.
• These are conflicting goals: we want to define a stable architecture to deal with
complexity, but we also want to allow flexibility to deal with change later in the
development process. This conflict can be solved by anticipating change and
designing for it, as sources of later changes tend to be the same for many systems:
• New vendor or new technology. Commercial components used to build the
system are often replaced by equivalent ones from a different vendor. This change
is common and generally difficult to cope with. The software marketplace is
dynamic, and vendors might go out of business before your project is
completed.
• New implementation. When subsystems are integrated and tested together, the
overall system response time is, more often than not, above performance
requirements. System performance is difficult to predict and should not be
optimized before integration. Developers should focus on the subsystem services
first. This triggers the need for more efficient data structures and algorithms—
often under time constraints.
• New views. Testing the software with real users uncovers many usability
problems. These often translate into the need to create additional views on the
same data.
• New complexity of the application domain. The deployment of a system triggers
ideas of new generalizations: a bank information system for one branch may
lead to the idea of a multi-branch information system. The application domain
itself might also increase in complexity: previously, flight numbers were
associated with one plane, and one plane only, but with air carrier alliances, one
plane can now have a different flight number from each carrier.
• Errors. Many requirements errors are discovered when real users start using the
system.
New vendor, new technology, new implementation.
Decouples i/f of class from implementation. Developer
not constrained with existing components.

New vendor, new technology, new implementation.


Decouples i/f of class from implementation. Developer
is constrained with existing components.

New vendor, new technology, new implementation.


Decouples algorithm from implementation. Implements
code at runtime.

New vendor, new technology. Creates families of related


objects. Prevents use of objects from incompatible
families.

New functionality. An object encapsulates all information


needed to perform an action. The information includes
method name, object that owns the method, etc.

New complexity of application domain. Common


superclass for aggregate and leaf nodes. New types of
leaves can be added without modifying existing code.
Encapsulating Data Stores with the Bridge
Pattern
• Consider the problem of incrementally developing, testing, and integrating
subsystems realized by different developers. Subsystems may be completed at
different times, delaying the integration of all subsystems until the last one is
completed. To avoid this delay, projects often use a stub implementation in place
of a specific subsystem so that the integration tests can start even before the
subsystems are completed. In other situations, several implementations of the
same subsystem are realized, such as a reference implementation that realizes the
specified functionality with the most basic algorithms, or an optimized
implementation that delivers better performance at the cost of additional
complexity. In short, a solution is needed for dynamically substituting
multiple realizations of the same interface for different uses.
• This problem can be addressed with the Bridge design pattern. For example, consider the
storage of Leagues in ARENA. In the early stages of the project, we are interested in a
rudimentary storage subsystem based on object serialization for the purpose of
debugging and testing the core use cases of the TournamentManagement subsystem. The
entity objects will be subject to many changes, and we do not know yet what performance
bottlenecks will be encountered during storage. Consequently, an efficient storage
subsystem should not be the focus of the first prototype. As discussed during the system
design of ARENA, however, we anticipate that both a file-based implementation and a
relational database implementation of the storage subsystem should be provided, in the
first and second iteration of the system, respectively. In addition, a set of stubs should be
provided to allow early integration testing even before the file-based implementation is
ready. To solve this problem, we apply the Bridge pattern shown in Figure 8-7. The
LeagueStore is the interface class to the pattern, and provides all high-level
functionality associated with storage. The LeagueStoreImplementor is an abstract
interface that provides the common interface for the three implementations, namely the
StubStoreImplementor for the stubs, the XMLStoreImplementor for the file-based
implementation, and the JDBCStoreImplementor for the relational database
implementation.
• Note that even if most LeagueStoreImplementors provide similar services, using a
Bridge abstraction reduces performance. The design goals we defined at the
beginning of system design help us decide about performance and modifiability
trade-offs.
Inheritance and delegation in the Bridge
pattern
• The Bridge pattern interface is realized by the Abstraction class, and its behavior
by the selected ConcreteImplementor class. The design pattern can be extended by
providing new RefinedAbstraction or ConcreteImplementor classes. This pattern
is a classic example of combining specification inheritance and delegation to
achieve both reuse and flexibility.
• On the one hand, specification inheritance is used between the abstract
Implementor interface and the classes ConcreteImplementors. As a result,
each ConcreteImplementor can be substituted transparently at runtime, from the
Abstraction class and RefinedAbstraction classes. This also ensures that, when
adding a new ConcreteImplementor, developers will strive to provide the same
behavior as all other ConcreteImplementors.
• On the other hand, Abstraction and Implementor are decoupled using
delegation. This enables the distribution of different behavior in each of the side
of the bridge. For example, the LeagueStore class in Figure 8-7 provides the
high-level behavior for storing Leagues, whereas the concrete
LeagueStoreImplementor provides specific lower-level functionality that differs
in its realization from one storage approach to the other. Since LeagueStore and
LeagueStoreImplementor provide different behaviors, they cannot be treated as
subtypes according to the Liskov Substitution Principle.
Encapsulating Legacy Components with the
Adapter Pattern
• As the complexity of systems increases and the time to market shortens, the
cost of software development significantly exceeds the cost of hardware.
Hence, developers have a strong incentive to reuse code from previous projects or
to use off-the-shelf components. Interactive systems, for example, are now rarely
built from scratch; they are developed with user interface toolkits that provide a
wide range of dialogs, windows, buttons, or other standard interface objects.
Interface engineering projects focus on reimplementing only part of an existing
system. For example, corporate information systems, costly to design and build,
must be updated to new client hardware. Often, only the client side of the system
is upgraded with new technology; the back end of the system left untouched.
Whether dealing with off-the-shelf component or legacy code, developers have to
deal with code they cannot modify and which usually was not designed for their
system.
• We deal with existing components by encapsulating them. This approach has the
advantage of decoupling the system from the encapsulated component, thus
minimizing the impact of existing software on the new design. This can be done
using an Adapter pattern.
• The Adapter design pattern converts the interface of a component into an interface
that the client expects. This interface is called the ClientInterface in Figure 8-5. An
Adapter class provides the glue between ClientInterface and LegacyClass. For
example, assume the client is the static sort() method of the Java Array class
(Figures 8-8 and 8-9). This method expects two arguments a, an Array of objects,
and c, a Comparator object, which provides a compare() method to define the
relative order between elements. Assume we are interested in sorting strings of the
class MyString, which defines the greaterThan() and an equals() methods. To sort
an Array of MyStrings, we need to define a new comparator,
MyStringComparator, which provides a compare() method using greaterThan()
and equals(). MyStringComparator is an Adapter class.
Figure 8-8 Applying the Adapter design pattern for sorting Strings in an Array (UML class diagram). See also source
code in Figure 8-9.
Inheritance and delegation in the Adapter pattern
• The Adapter pattern uses specification inheritance between the ClientInterface
and the Adapter. The Adapter in turn delegates to the LegacyClass implementor
class to realize the operations declared in ClientInterface. On the one hand, this
enables all client code that already uses the ClientInterface to work with instances
of Adapter transparently and without modification of the client. On the other hand,
the same Adapter can be used for subtypes of the LegacyClass.
• Note that the Bridge and the Adapter patterns are similar in purpose and structure.
Both decouple an interface from an implementation, and both use a specification
inheritance relationship and a delegation relationship. They differ in the context in
which they are used and in the order in which delegation and inheritance occur.
The Adapter pattern uses inheritance first and then delegation, whereas the Bridge
pattern uses delegation first and then inheritance. The Adapter pattern is applied
when the interface (i.e., ClientInterface) and the implementation (i.e.,
LegacyClass) already exist and cannot be modified. When developing new code,
the Bridge pattern is a better choice as it provides more extensibility.
Encapsulating Context with the Strategy
Pattern
• Consider a mobile application running on a wearable computer that uses
different networks protocols depending on the location of the user: assume, for
example, a car mechanic using the wearable computer to access repair manuals
and maintenance records for the vehicle under repair. The wearable computer
should operate in the shop with access to a local wireless network as well as on
the roadside using a third-generation mobile phone network, such as UMTS.
When updating or configuring the mobile application, a system administrator
should be able to use the wearable computer with access to a wired network such
as Ethernet. This means that the mobile application needs to deal with different
types of networks as it switches between networks dynamically, based on factors
such as location and network costs. Assume that during the system design of this
application, we identify the dynamic switching between wired and wireless
networks as a critical design goal. Furthermore we want to be able to deal with
future network protocols without having to recompile the application.
• To achieve both of these goals, we apply the Strategy design pattern. The system
model and implementation, respectively, are shown in Figures 8-10 and 8-11. The
Strategy class is realized by NetworkInterface, which provides the common
interface to all networks; the Context class is realized by a NetworkConnection
object, which represents a point-to-point connection between the wearable and a
remote host. The Client is the mobile application. The Policy is the
LocationManager, which monitors the current location of the wearable and the
availability of networks, and configures the NetworkConnection objects with the
appropriate NetworkInterfaces. When the LocationManager object invokes the
setNetworkInterface() method, the NetworkConnection object shuts down the
current NetworkInterface and initializes the new NetworkInterface transparently
from the rest of the application.
Figure 8-10 Applying the Strategy pattern for encapsulating multiple implementations of a NetworkInterface (UML
class diagram). The LocationManager implementing a specific policy configures NetworkConnection with a concrete
NetworkInterface (i.e., the mechanism) based on the current location. The Application uses the NetworkConnection
independently of concrete NetworkInterfaces. See corresponding Java code in Figure 8-11.
Inheritance and delegation in the Strategy pattern
• The class diagrams for the Bridge and the Strategy patterns (see Figures 8-7 and 8-
10) are almost identical. The key difference is in the creator of the concrete
implementation classes: In the Bridge pattern, the class Abstraction creates and
initializes the ConcreteImplementations. In the Strategy pattern, however, the
Context is not aware of the ConcreteStrategies. Instead, a client creates the
ConcreteStrategy objects and configures the Context. Moreover,
ConcreteImplementations in the Bridge pattern are usually created at initialization
time, while ConcreteStrategies in the Strategy pattern are usually created and
substituted several times during run time.
Encapsulating Platforms with the Abstract
Factory Pattern
• Consider an application for an intelligent house: the application receives events
from sensors distributed throughout the house (e.g., light bulb on, light bulb off,
window open, window closed, inside and outside temperature, weather
forecasts), identifies predefined patterns, and issues commands for actuators (e.g.,
turn air-conditioning on, store statistics on energy consumption, close garage door,
trigger theft alarm). Although several manufacturers provide the hardware to
build such applications (e.g., EIB, Zumtobel’s Luxmate), interoperability in
this domain is currently poor, preventing the mix and match of devices from
different manufacturers, and thus, making it difficult to develop a single software
solution for all manufacturers.
• We use the Abstract Factory design pattern to solve this problem. In our intelligent
house, each manufacturer provides temperature sensors, electric blinds that report
if they are forced in, and intelligent light bulbs that report if they have burned out.
As shown in Figure 8-12, these generic objects are called AbstractProducts (e.g.,
LightBulb, Blind), and their concrete realizations are called ConcreteProducts
(e.g., EIBLightBulb, ZumtobelLightBulb, EIBBlind, ZumtobelBlind). One factory
for each manufacturer (e.g., ZumtobelFactory, EIBFactory) provides methods for
creating the ConcreteProducts (e.g., createLightBulb(), createBlind()). The Client
classes (e.g., a TheftApplication) access only the interfaces provided by the
AbstractFactory and the AbstractProducts, thereby shielding the Client classes
completely from the manufacturer of the underlying products.
EIBBlind LuxmateBlind

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()

• An example of a postcondition for the acceptPlayer() method is that the current


number of Players must be exactly one more than the number of Players before the
invocation of acceptPlayer(). We can express this postcondition as

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.

• We use invariants, preconditions, and postconditions to specify special or


exceptional cases unambiguously. It is also possible to use constraints to
completely specify the behavior of an operation. Such a use of constraints, called
“constraint-based specification,” however, is difficult and can be more
complicated than implementing the operation itself.
Object Constraint Language
• A constraint can be expressed in natural language or in a formal language such
as Object Constraint Language (OCL). OCL is a language that allows constraints
to be formally specified on single model elements (e.g., attributes, operations,
classes) or groups of model elements (e.g., associations and participating classes).
• A constraint is expressed as a boolean expression returning the value True or
False. A constraint can be depicted as a note attached to the constrained UML
element by a dependency relationship. Figure 9-4 depicts a class diagram of
Tournament example of the previous section using UML and OCL.
Figure 9-4 Examples of invariants, preconditions, and postconditions in OCL attached as notes to the UML model
(UML class diagram).
• Attaching OCL expressions to diagrams can lead to clutter. For this reason, OCL
expressions can be alternatively expressed in a textual form. For example, the
invariant for the Tournament class requiring the attribute maxNumPlayers to be
positive is written as follows:

• 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:

1. A Tournament’s planned duration must be under one week.


2. Players can be accepted in a Tournament only if they are already registered with
the corresponding League.
3. The number of active Players in a League are those that have taken part in at least
one Tournament of the League.
Figure 9-6 Associations among League, Tournament, and Player classes in ARENA.
• To better understand these constraints, let’s examine them for a specific group of
instances (see Figure 9-7). The tttExpert:League includes four Players, alice, bob,
marc, and joe, and two Tournaments, a winter:Tournament and a
xmas:Tournament. alice and bob are competing in the winter:Tournament while
bob, marc, and joe are competing in the xmas:Tournament. The
chessNovice:League currently only includes one Player, zoe, and no Tournaments.
Now, let’s review the above constraints in terms of the instances of Figure 9-7:
1. The winter:Tournament lasts two days, the xmas:Tournament three days, both
under a week.
2. All Players of the winter:Tournament and the xmas:Tournament are associated
with tttExpert:League. The Player zoe, however, is not part of the
tttExpert:League and does not take part in either Tournament.
3. tttExpert:League has four active Players, whereas the chessNovice:League has
none, because zoe does not take part in any Tournament.
Figure 9-7 Example with two Leagues, two Tournaments, and five Players (UML object diagram).
• At first sight, these constraints vary quite a bit: for example, the first constraint
involves attributes of a single class (Tournament.start and Tournament.end); the
second one involves three classes (i.e., Player, Tournament, League) and their
associations; the third involves a set of Matches within a single Tournament. In all
cases, we start with the class of interest and navigate to one or more classes in the
model. In general, we distinguish three cases of navigation (Figure 9-8):
• Local attribute. The constraint involves an attribute that is local to the class of
interest (e.g., duration of a Tournament in constraint 1),
• Directly related class. The expression involves the navigation of a single
association to a directly related class (e.g., Players of a Tournament, League of a
Tournament).
• Indirectly related class. The constraint involves the navigation of a series of
associations to an indirectly related class (e.g., the Players of all Tournaments of a
League).
Figure 9-8 There are only three basic types of navigation. Any OCL constraint can be built using a combination of these
three types.
• All constraints can be built using a combination of these three basic cases of
navigation. Once we know how to deal with these three navigation cases, we can
build any constraint. We already know how to deal with the first type of constraint
with the dot notation, as we saw in the previous section. For example, we can
write constraint 1 as follows:

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:

You might also like