0% found this document useful (0 votes)
4 views

Module 4 Advance Software engineering

Module-4 covers object design, implementation, and testing, focusing on reusing design patterns and specifying interfaces. It discusses the balance between stability and flexibility in system design, emphasizing the importance of anticipating future changes and selecting appropriate design patterns. The module also details various design patterns such as Bridge, Adapter, Strategy, Abstract Factory, Command, and Composite, along with their applications and benefits in software development.

Uploaded by

mriconic046
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views

Module 4 Advance Software engineering

Module-4 covers object design, implementation, and testing, focusing on reusing design patterns and specifying interfaces. It discusses the balance between stability and flexibility in system design, emphasizing the importance of anticipating future changes and selecting appropriate design patterns. The module also details various design patterns such as Bridge, Adapter, Strategy, Abstract Factory, Command, and Composite, along with their applications and benefits in software development.

Uploaded by

mriconic046
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 50

Module-4

OBJECT DESIGN, IMPLEMENTATION AND TESTING


 Object design-Reusing pattern solutions:

 An Overview of Object Design,

 Reuse Concepts: Design Patterns

 Reuse Activities

 Managing Reuse

 Object design-Specifying interface:

 An overview of interface specification

 Interfaces Specification Concepts

 Interfaces Specification Activities

 Managing Object Design

 Mapping model to code: Mapping Models to Code Overview,

Mapping Concepts, Mapping Activities, Managing

Implementation,

 Testing: An overview of testing, Testing concepts, Managing

testing.
 Object design-Reusing pattern solutions:

 An Overview of Object Design

Conceptually, software system development fills the gap between a given problem and an
existing machine. The activities of system development incrementally close this gap by
identifying and defining objects that realize part of the system (Figure 8-1).

Figure8-1 Object design closes the gap between application objects identified during
requirements and off-the-shelf components selected during system design (stylized UML
class diagram).

Analysis reduces the gap between the problem and the machine by identifying objects
representing problem-specific concepts. During analysis the system is described in terms of
external behavior such as its functionality (use case model), the application domain concepts
it manipulates (object model), its behavior in terms of interactions (dynamic model), and its
nonfunctional requirements.

System design reduces the gap between the problem and the machine in two ways. First,
system design results in a virtual machine that provides a higher level of abstraction than the
machine. This is done by selecting off-the-shelf components for standard services such as
middleware, user interface toolkits, application frameworks, and class libraries. Second,
system design identifies off-the-shelf components for application domain objects such as
reusable class libraries of banking objects.

After several iterations of analysis and system design, the developers are usually left with a
puzzle that has a few pieces missing. These pieces are found during object design. This
includes identifying new solution objects, adjusting off-the-shelf components, and precisely
specifying each subsystem interface and class. The object design model can then be
partitioned into sets of classes that can be implemented by individual developers.

 Reuse Concepts: Design Patterns

Design Patterns
In object-oriented development, design patterns are template solutions that developers have
refined over time to solve a range of recurring problems [Gamma et al., 1994]. A design
pattern has four elements:

 A name that uniquely identifies the pattern from other patterns.


 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.
 A solution stated as a set of collaborating classes and interfaces.
 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.
Figure8-5 An example of design pattern, Adapter (adapted from [Gamma et al., 1994]).

 Reuse Activities: Selecting Design Patterns and Components

This passage talks about a challenge in software development: balancing stability and
flexibility in system design. Here’s a simpler explanation:

 System Design: During this stage, developers break the system into smaller parts,
called subsystems, to manage complexity. Each subsystem is isolated from the others,
meaning changes in one subsystem won’t directly affect others. This creates a solid
structure, but it can also limit flexibility.
 Object Design: This focuses on making the software easy to change and adapt later.
The goal is to minimize the cost of future changes (like adding new features or fixing
bugs). However, creating this flexibility might make the initial design more complex.

The paradox is that stability (from system design) and flexibility (from object design) seem
to conflict. On one hand, we want a stable, solid structure to manage complexity, but on the
other hand, we also want flexibility to allow changes in the future.

To solve this paradox, the solution is anticipating change early on. Developers should
design with future changes in mind because they often come from similar sources. These
sources of change include:

1. New Vendor or Technology: Software often relies on third-party tools or


components. These might change or be replaced by different versions over time. For
example, one company’s tool may be replaced by another, or a company might go out
of business.
2. New Implementation Needs: After testing the system as a whole, developers may
find that it doesn’t meet performance goals (like speed). This can require optimizing
the system’s design, but only after testing how all the parts work together.
3. New Views (Interfaces): As real users test the system, they may discover usability
issues that require creating different ways to interact with the data or system (like a
new interface or dashboard).
4. New Complexity in the Domain: Sometimes, after using the system, new
requirements emerge. For example, a banking system that worked for a single branch
might need to be extended to work for multiple branches. Or, a plane's flight number
may change based on different airline alliances.
5. Errors: Real-world use often uncovers problems or mistakes that weren’t caught
during development, requiring fixes or adjustments.

By designing systems with these types of changes in mind, developers can reduce the cost
and impact of future changes, keeping the system both stable and flexible.

After identifying a design pattern for each type of anticipated change (Table 8-1), we discuss
the pattern in the context of an actual situation and, in each case, discuss how inheritance and
delegation are used as building blocks to achieve modifiability and extensibility.

Table8-1 Selected design patterns and the changes they anticipate.


1. Encapsulating Data Stores with the Bridge Pattern

Here’s a simplified breakdown of the explanation in points:

1. Problem:
o The ARENA system needs a storage method for Leagues, but at first, it only
needs a simple storage system for testing.
o The team doesn't know which storage method will be best or face performance
issues yet, so they want to keep things flexible.
2. Goal:
o In later stages, they plan to use different storage systems (e.g., file-based and
database-based), but they want to manage these without constantly changing
the rest of the system.
3. Solution: Use the Bridge Design Pattern:
o Bridge pattern separates the high-level storage logic from the details of the
storage method (file, database, etc.), making it easier to switch between
storage types without changing the whole system.
4. Components of the Bridge Pattern:
o LeagueStore: The interface that provides high-level operations for storage. It
doesn’t care about how storage is done (just knows how to store and retrieve
data).
o LeagueStoreImplementor: An abstract interface that defines how different
storage types should behave.
o Implementations:
1. StubStoreImplementor: A simple placeholder for testing (doesn't
store real data).
2. XMLStoreImplementor: Stores data in files (XML format).
3. JDBCStoreImplementor: Stores data in a relational database.
5. Benefit:
o The Bridge pattern allows the system to switch between storage methods
(file, database) easily without needing to rewrite other parts of the system.

Figure 8-7 Applying the Bridge design pattern for abstracting database vendors (UML class
diagram).
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

Inheritance and delegation in the Bridge pattern

 Inheritance is used to define common behaviors across different storage implementations.


 Delegation helps keep high-level and low-level behaviors separate, allowing easy updates
or changes to storage methods.
 By using both, you can extend the system with new storage methods without breaking
existing functionality.

2. Encapsulating Legacy Components with the Adapter Pattern

Here’s a simple breakdown in points:

1. Problem:
o Software development is costly, especially as systems become more complex
and deadlines shorten.
o Developers reuse old code or use off-the-shelf components (pre-made
software parts) to save time and money.
2. Example:
o User interfaces (like buttons, windows) are often built using pre-made
toolkits instead of building everything from scratch.
o When updating systems, only certain parts (like the client-side) may need
upgrading, while the backend stays the same.
3. Challenge:
o Developers work with existing code (or old software), which they can’t
modify and wasn’t designed for the new system.
4. Solution - Adapter Pattern:
o To deal with this, developers encapsulate the existing components using the
Adapter pattern.
o The Adapter acts as a translator between the old component and the new
system.
o This decouples the new system from the old code, reducing its impact on the
design.

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.
3. Encapsulating Context with the Strategy Pattern

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.

To achieve both of these goals, we apply the Strategy design pattern (Appendix A.9, [Gamma
et al., 1994]). 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.

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.
4. Encapsulating Platforms with the Abstract Factory Pattern
We use the Abstract Factory design pattern (Appendix A.1) 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.

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

5. Encapsulating Control Flow with the Command Pattern


We can apply the Command design pattern (Appendix A.4, [Gamma et al., 1994]) 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.

6. Encapsulating Hierarchies with the Composite Design Pattern


Figure8-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.

Swing addresses this problem with the Composite design pattern (Appendix A.5, [Gamma et
al., 1994]) 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.

7. Heuristics for Selecting 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.

8. Identifying and Adjusting Application Frameworks


Application frameworks

An application framework is a reusable partial application that can be specialized to produce


custom applications [Johnson & Foote,1988]. 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.

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

 Managing Reuse
Historically, software development started as a craft, in which each application was custom
made according to the wishes and needs of a single customer. After all, software development
represented only a fraction of the cost of hardware, and computing solutions were affordable
only to few. With the price of hardware dropping and computing power increasing
exponentially, the number of customers and the range of applications has broadened
dramatically

Reuse, whether design patterns, frameworks, or components, has many technical and
managerial advantages:
Benefits of Reusing Design Patterns and Components:
1. Lower Development Effort:
o Reusing solutions or components avoids common mistakes.
o Systems using design patterns are easier to extend and adapt to changes,
reducing development effort.
o This saves resources, allowing more focus on testing the software, improving
its quality.
2. Lower Risk:
o When using the same design patterns or components repeatedly, the common
problems are already known.

o The time required to adjust or integrate these patterns/components is


predictable, making the development process more reliable and less risky.
3. Widespread Use of Standard Terms:
o Reusing standard design patterns (like Adapter, Bridge, Command, Facade)
helps establish a shared vocabulary among developers.
o This reduces confusion and ensures everyone understands the same concepts,
improving communication.
4. Increased Reliability:
o While reuse itself doesn't guarantee reliability (e.g., the Ariane 501 incident
showed that), it can still improve reliability in a few ways:

 Faster development allows more time for testing.


 Repetitive use of components builds a knowledge base to anticipate
and fix common problems.
 A shared vocabulary reduces misunderstandings and
miscommunications, helping prevent mistakes.

1. Documenting Reuse
Reuse activities involve two types of documentation: the documentation of the template
solution being reused and the documentation of the system that is reusing the solution.

The documentation of a reusable solutions (e.g., the design pattern, a framework, or a


component) includes not only a description of the solution, but also a description of the class
of problems it addresses, the trade-offs faced by the developer, alternative implementations,
and examples of use.
The documentation of the system under construction should minimally include references to
all the reused solutions. For example, design patterns are not immediately identifiable in the
code, as the classes involved usually have names different from names used in the standard
pattern. Many patterns draw their benefits from the decoupling of certain classes

2. Assigning Responsibilities
Below are the main roles involved in reuse:

• Component expert. The component expert is familiar with using a specific component.
The component expert is a developer and usually has received third-party training in the use
of the component.

• Pattern expert. The pattern expert is the analog of the component expert for a family of
design patterns. However, pattern experts are usually self-made and acquire their knowledge
from experience.

• Technical writer. The technical writer must be aware of reuse and document dependencies
between components, design patterns, and the system, as discussed in the previous section.
This may require the technical writer to become familiar with the solutions typically reused
by the organization and with their associated terms.

• Configuration manager. In addition to tracking configurations and versions of individual


subsystems, the configuration manager must also be aware of the versions of the components
that are used. While newer versions of the components may be used, their introduction
requires tests to be repeated and changes related to the upgrade documented.
Object design-Specifying interface:

 An overview of interface specification


All these models, however, reflect only a partial view of the system. Many puzzle pieces are
still missing and many others are yet to be refined. The goal of object design is to produce an
object design model that integrates all of the above information into a coherent and precise
whole. The goal of interface specification, the focus of this chapter, is to describe the
interface of each object precisely enough so that objects realized by individual developers fit
together with minimal integration 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


In this section, we present the principal concepts of interface specification:

• Class Implementor, Class Extender, and Class User (Section 9.3.1)

• Types, Signatures, and Visibility (Section 9.3.2)

• Contracts: Invariants, Preconditions, and Postconditions (Section 9.3.3)

• Object Constraint Language (Section 9.3.4)

• OCL Collections: Sets, Bags, and Sequences (Section 9.3.5)

• OCL Qualifiers: forAll and exists (Section 9.3.6)


1. 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):

Figure 9-1 The Class Implementor, the Class Extender, and the Class User role (UML use
case diagram)

• Theclass 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.

• Theclass 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.

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.
Figure 9-2 ARENA Game abstract class with user classes and extender classes.

2. Types, Signatures, and Visibility


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.

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.

3. 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 [Meyer, 1997].

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.

4. Object Constraint Language


A constraint can be expressed in natural language or in a formal language such as Object
Constraint Language (OCL) [OMG, 2006]. 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). In the next two sections, we
introduce the basic syntax of OCL. For a complete tutorial on OCL, we refer to [Warmer &
Kleppe, 2003].

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)

5. 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

OCL sets are used when navigating a single association.

OCL sequences are used when navigating a single ordered association

OCL bags are multisets: they can contain the same object multiple times. Bags are used to
accumulate the objects when accessing indirectly related objects.

6. 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.

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,

Interface Specification Activities

 Identifying Missing Attributes and Operations


 Specifying Type Signatures and Visibility
 Specifying Preconditions and Postconditions
 Specifying Invariants
 Inheriting Contracts

1. Identifying Missing Attributes and Operations

Figure9-9 Analysis objects of ARENA identified during the analysis of announce


Tournament use case (UML class diagram). Only selected information is shown for brevity.
During this step, we examine the service description of the subsystem and identify missing
attributes and operations. During analysis, we may have missed many attributes because we
focused on the functionality of the system: we described the functionality of the system
primarily with the use case model (as opposed to operations in the object model). We focused
on the application domain when constructing the object model and therefore ignored details
related to the system that are independent of the application domain.

In the design of ARENA, we noticed that the server's resources get heavily used by the
number of tournaments and matches happening at the same time, which creates a bottleneck.
We also realized that if players try to play multiple matches at once, it doesn't help with ad
revenue (since showing multiple ads at the same time doesn't increase income) and actually
reduces the quality of the game (since players are distracted and make mistakes).

So, we decided to add checks in the system during the ApplyForTournament process to
stop players from entering multiple matches at once. However, we don't want to punish
players who participate in many tournaments one after another because they help improve
both the quality of the game and ad revenue.

Figure 9-10 A sequence diagram for the applyForTournament() operation (UML sequence
diagram). This sequence diagram leads to the identification of a new operation,
isPlayerOverbooked() to ensure that players are not assigned to Tournaments that take place
simultaneously.

To prevent a player from applying to two different tournaments that will be conducted at the
same time, we draw a sequence diagram representing the control and data flow needed
(Figure 9-10). Drawing this diagram leads us to the identification of an additional operation,
isPlayerOverbooked(), that checks if the start and end dates of the Tournament of interest
overlap with those of other Tournaments into which the Player has already been accepted.
2. Specifying Types, Signatures, and Visibility
During this step, we specify the types of the attributes, the signatures of the operations, and
the visibility of attributes and operations. Specifying types refines the object design model in
two ways. First, we add detail to the model by specifying the range of each attribute.

For example, by determining the type of the start and end date of a Tournament, we make
decisions about the granularity of the time tracked by the application. By selecting a
representation of time including days, hours, minutes, and seconds, we enable LeagueOwners
to conduct several Tournaments per day. Second, we map classes and attributes of the object
model to built-in types provided by the development environment. For example, by selecting
String to represent the name attributes of Leagues and Tournaments, we can use all the
operations provided by the String class to manipulate name values

Figure9-11 Adding type information to the object model of ARENA (UML class diagram).
Only selected information is shown for brevity.

3. Specifying Pre- and Postconditions


During this step, we define contracts for each public operation of each class. We already said
that a contract is an agreement between the class user and the class implementor. The
preconditions of an operation describe the part of the contract that the class user must respect.
The postconditions describe what the class implementor guarantees in the event the class user
fulfilled her part of the contract. When refining a class, class extenders inherit the contract
from the original class implementor.

Preconditions and postconditions can also be used to specify dependencies among operations
in the same class. Consider,

for example, the operations on the TournamentControl class. Given a new Tournament,
these operations must be invoked in a specific order. We cannot resolve the sponsorship of a
Tournament without knowing which sponsors are interested. Also, we cannot advertise the
Tournament before we resolve the sponsorship issue. For TournamentControl, we can simply
write preconditions and postconditions that examine the state of the associations of the
Tournament class. To state that sponsors cannot be selected before there are interested
advertisers, we write the following:

To ensure that TournamentControl.selectSponsors() is invoked only once, we add the


following precondition:

Finally, to specify how TournamentControl.selectSponsors() sets the advertisers association,


we add the following postcondition:

4. Specifying Invariants
Invariants are much more difficult to write than preconditions and postconditions, but they
provide an overview of the essential properties of the class. Invariants constitute a permanent
contract that extends and overwrites the operation-specific contracts. The activity of
identifying invariants is similar to that of finding abstract classes during analysis
(Section5.4.10). A few are obvious and can be written from the start. Others can be identified
by extracting common properties from operation-specific contracts.

An example of an obvious invariant is that all Matches of a Tournament must occur within
the time frame of the Tournament:
5. Inheriting Contracts
In a polymorphic language, a class can be substituted by any of its descendents. That is, a
class user invoking operations on a class could be invoking instead a subclass. Hence, the
class user expects that a contract that holds for the superclass still holds for the subclass. We
call this contract inheritance.

For example, in ARENA, consider the inheritance hierarchy between User, LeagueOwner,
Player, Spectator, and Advertiser (Figure 9-13). The User class has an invariant stating that
the email address should be not null so that each user can be notified. If at some point in our
design, we decide that Spectators do not really need an E-mail address, then this contract will
be broken and classes invoking the User.notify() method may break. Consequently, either
Spectators should be taken out of the User hierarchy (i.e., Spectator does not fulfill the User
contract) or the invariant should be revised (i.e., the terms of the contract should be
reformulated).

Figure 9-13 A simple example of contract inheritance: An invariant specified in a superclass


must hold for all of its subclasses (UML class diagram with OCL constraint).

Managing Object Design


In this section, we discuss management issues related to object design. There are two primary
management challenges during object design:

• Increased communication complexity. The number of participants involved during this


phase of development increases dramatically. The object design models and code are the
result of the collaboration of many people. Management needs to ensure that decisions among
these developers are made consistently with project goals.

• Consistency with prior decisions and documents. Developers often do not appreciate
completely the consequences of analysis and system design decisions before object design.
When detailing and refining the object design model, developers may question some of these
decisions and reevaluate them. The management challenge is to maintain a record of these
revised decisions and to make sure all documents reflect the current state of development
We discuss these challenges in Section 9.5.1, where we focus on the Object Design
Document, its development and maintenance, and its relationship with other documents, and
in Section 9.5.2, where we describe the roles and responsibilities associated with object
design.

1. Documenting Object Design


Object design is documented in the Object Design Document (ODD). It describes object
design trade-offs made by developers, guidelines they followed for subsystem interfaces, the
decomposition of subsystems into packages and classes, and the class interfaces.

The ODD is used to exchange interface information among teams and as a reference during
testing. The audience for the ODD includes system architects (i.e., the developers who
participate in the system design), developers who implement each subsystem, and testers.

Here’s a simplified breakdown of the three main approaches to documenting object design
(ODD):

1. Self-contained ODD generated from the model:


o Document the object design in the same way as the system analysis model
(using UML).
o Automatically generate the document from the model.
o Disadvantages:
 Redundant with the Requirements Analysis Document (RAD).
 High effort to keep both RAD and ODD consistent.
 The document often becomes out of date because it needs to match the
source code.
2. ODD as an extension of the RAD:
o Treat the object design as an extension of the analysis model.
o The object design includes both application objects and solution objects
(objects added to solve design problems).
o Advantages:
 Easier to keep RAD and ODD consistent because there is less
redundancy.
3. ODD embedded into source code:
o Embed object design details directly into the source code using tagged
comments.
o Use modeling tools to generate class stubs and describe class interfaces with
comments.
o Advantages:
 Easier to maintain consistency between object design and source code.
 When code changes, the comments and object design are updated
automatically.
o Focus: This section will focus on this approach.

In short, the third approach is preferred because it directly links the object design with the
source code, making it easier to update and keep everything consistent.

Figure 9-14 Embedded ODD approach. Class stubs are generated from the object design
model. The object design model is then documented as tagged comments in the source code.
The initial object design model is abandoned and the ODD is generated from the source code
instead using a tool such as Javadoc (UML activity diagram).

2. Assigning Responsibilities
Object design is characterized by a large number of participants accessing and modifying a
large amount of information. To ensure that changes to interfaces are documented and
communicated in an orderly manner, several roles collaborate to control, communicate, and
implement changes. These include the members of the architecture team who are responsible
for system design and subsystem interfaces, liaisons who are responsible for interteam
communication, and configuration managers who are responsible for tracking change.

Here’s a simplified explanation of the roles and responsibilities during object design:
1. Core Architect:
o Creates coding guidelines and conventions before object design starts.
o Ensures all architects and developers follow these conventions.
o Makes sure the object design aligns with previous decisions documented in the
System Design Document (SDD) and Requirements Analysis Document
(RAD).
2. Architecture Liaisons:
o Document the public subsystem interfaces they are responsible for.
o Create the first draft of the Object Design Document (ODD) for developers to
use.
o Work on changes to public interfaces and ensure that developers are notified
about any updates to avoid miscommunication.
o Work with the core architect to form the architecture team.
3. Object Designers:
o Refine and detail the interface specifications for the class or subsystem they
are implementing.
4. Configuration Manager:
o Releases changes to the interfaces and ODD once they are ready.
o Tracks the relationship between the source code and ODD revisions to
maintain consistency.
5. Technical Writers:
o Clean up and finalize the ODD document.
o Ensure the document is consistent in structure and content.
o Check that the document follows the coding and design guidelines.

Each role helps ensure the object design is well-documented, consistent, and aligned with the
overall system architecture.

3. Using Contracts During Requirements Analysis

Here’s a simplified explanation of the trade-offs when deciding if and when to use
constraints (like OCL) during the software development process:

1. Communication Among Stakeholders:


o Different models help communicate with different stakeholders (e.g., clients,
developers).
o Use cases or UI mock-ups are easier for clients to understand than OCL
constraints, which are more precise and useful for developers.
o Trade-off: Use simple models (like use cases) for clients and precise ones
(like OCL) for developers, depending on the audience.
2. Level of Detail and Rate of Change:
o Attaching constraints to the analysis model requires a deep understanding of
the requirements. If this understanding is available early, it leads to a more
complete model.
o Trade-off: If this information isn’t available early, making decisions too soon
can lead to a high rate of change later on, which increases costs and time.
3. Level of Detail and Elicitation Effort:
o Getting detailed information from users during the analysis phase can be hard
and time-consuming.
o Trade-off: It’s easier to get detailed information later when early versions of
the system are available, but this approach assumes changes won’t be too
costly to make later, especially for UI or dialog-related issues.
4. Testing Requirements:
o For automated testing or strict testing (e.g., in industries like traffic control or
medicine), you need a precise specification to compare the actual system
behavior against.
o Trade-off: Specifying constraints early on helps with testing, but only if it’s
crucial to have precision for automated or rigorous tests.

In short, you need to carefully consider the timing and level of detail required for constraints
based on who you’re communicating with, how much information you have early on, and the
needs of testing.

Mapping Models to Code

 An Overview of Mapping
A transformation aims at improving one aspect of the model (e.g., its modularity) while
preserving all of its other properties (e.g., its functionality). Hence, a transformation is
usually localized, affects a small number of classes, attributes, and operations, and is
executed in a series of small steps. These transformations occur during numerous object
design and implementation activities. We focus in detail on the following activities:

 Optimization This activity addresses the performance requirements of the system


model. This includes reducing the multiplicities of associations to speed up queries,
adding redundant associations for efficiency, and adding derived attributes to improve
the access time to objects.
 Realizing associations During this activity, we map associations to source code
constructs, such as references and collections of references
 Mapping contracts to exceptions. During this activity, we describe the behavior of
operations when contracts are broken. This includes raising exceptions when
violations are detected and handling exceptions in higher level layers of the system.
 Mapping class models to a storage schema During system design, we selected a
persistent storage strategy, such as a database management system, a set of flat files,
or a combination of both. During this activity, we map the class model to a storage
schema, such as a relational database schema.
 Mapping Concepts
We distinguish four types of transformations (Figure 10-1)

 Model transformations operate on object models (Section 10.3.1). An example is the


conversion of a simple attribute (e.g., an address represented as a string) to a class
(e.g., a class with street address, zip code, city, state, and country attributes).
 Refactorings are transformations that operate on source code (Section 10.3.2). They
are similar to object model transformations in that they improve a single aspect of the
system without changing its functionality. They differ in that they manipulate the
source code.
 Forward engineering produces a source code template that corresponds to an object
model (Section 10.3.3). Many modeling constructs, such as attribute and association
specifications, can be mechanically mapped to source code constructs supported by
the selected programming language (e.g., class and field declarations in Java), while
the bodies and additional private methods are added by developers.
 Reverse engineering produces a model that corresponds to source code (Section
10.3.4). This transformation is used when the design of the system has been lost and
must be recovered from the source code. Although several CASE tools support
reverse engineering, much human interaction is involved for recreating an accurate
model, as the code does not include all information needed to recover the model
unambiguously.

Figure 10-1 The four types of transformations described in this chapter: model
transformations, refactorings, forward engineering, and reverse engineering

OPTION

1. Model Transformation
Analysis, we used transformations to organize objects into inheritance hierarchies and
eliminate redundancy from the analysis model. For example, the transformation in Figure 10-
2 takes a class model with a number of classes that contain the same attribute and removes
the redundancy. The Player, Advertiser, and LeagueOwner in ARENA all have an email
address attribute. We create a superclass User and move the email attribute to the superclass.
Figure 10-2 An example of an object model transformation. A redundant attribute can be
eliminated by creating a superclass.

2. Refactoring
A refactoring is a transformation of the source code that improves its readability or
modifiability without changing the behavior of the system

For example, the object model transformation of Figure 10-2 corresponds to a sequence of
three refactorings. The first one, Pull Up Field, moves the email field from the subclasses to
the superclass User. The second one, Pull Up Constructor Body, moves the initialization code
from the subclasses to the superclass. The third and final one, Pull Up Method, moves the
methods manipulating the email field from the subclasses to the superclass. Let’s examine
these three refactorings in detail.

Pull Up Field relocates the email field using the following steps (Figure 10-3): 1. Inspect
Player, LeagueOwner, and Advertiser to ensure that the email field is equivalent. Rename
equivalent fields to email if necessary. 2. Create public class User. 3. Set parent of Player,
LeagueOwner, and Advertiser to User. 4. Add a protected field email to class User. 5.
Remove fields email from Player, LeagueOwner, and Advertiser. 6. Compile and test.
Figure 10-3 Applying the Pull Up Field refactoring.

3. Forward Engineering
Forward engineering is applied to a set of model elements and results in a set of
corresponding source code statements, such as a class declaration, a Java expression, or a
database schema. The purpose of forward engineering is to maintain a strong correspondence
between the object design model and the code, and to reduce the number of errors introduced
during implementation, thereby decreasing implementation effort.

For example, Figure 10-5 depicts a particular forward engineering transformation applied to
the classes User and LeagueOwner. First, each UML class is mapped to a Java class. Next,
the UML generalization relationship is mapped to an extends statement in the LeagueOwner
class. Finally, each attribute in the UML model is mapped to a private field in the Java
classes and to two public methods for setting and getting the value of the field. Developers
can then refine the result of the transformation with additional behavior, for example, to
check that the new value of maxNumLeagues is a positive integer.

Figure 10-5 Realization of the User and LeagueOwner classes (UML class diagram and Java
excerpts). In this transformation, the public visibility of email and maxNumLeagues denotes
that the methods for getting and setting their values are public. The actual fields representing
these attributes are private.
4. Reverse Engineering
Reverse engineering is applied to a set of source code elements and results in a set of model
elements. The purpose of this type of transformation is to recreate the model for an existing
system, either because the model was lost or never created, or because it became out of sync
with the source code.

Reverse engineering is essentially an inverse transformation of forward engineering. Reverse


engineering creates a UML class for each class declaration statement, adds an attribute for
each field, and adds an operation for each method. However, because forward engineering
can lose information (e.g., associations are turned into collections of references),

reverse engineering does not necessarily recreate the same model. Although many CASE
tools support reverse engineering, CASE tools provide, at best, an approximation that the
developer can use to rediscover the original model.

5. Transformation Principles
A transformation aims at improving the design of the system with respect to some criterion.
We discussed four types of transformations so far: model transformations, refactorings,
forward engineering, and reverse engineering.

Here are the principles to follow during object model transformations to avoid introducing
errors:

1. Single Criteria Transformation:


o Each transformation should focus on improving one specific design goal, such
as response time or coherence.
o Avoid trying to optimize multiple criteria at once, as this can make the code
too complex and increase the risk of errors.
2. Local Transformation:
o Transformations should only affect a small part of the system, like a few
methods or classes.
o If you need to change an interface (e.g., adding a parameter), make sure to
change client classes one at a time. For backward compatibility, keep the older
methods available for testing.
o If you’re changing many subsystems at once, you’re likely making an
architectural change, not an object model transformation.
3. Apply Transformations in Isolation:
o Apply one transformation at a time. For example, if you’re optimizing a
method, don’t add new functionality at the same time.
o This approach helps you focus on one issue at a time and reduces the chance
of introducing errors.
4. Follow Each Transformation with Validation:
o After completing a transformation, validate the changes before moving on to
the next one.
o If it’s an object model transformation, update related sequence diagrams and
review related use cases to ensure correct functionality.
 Mapping Activities
In this section, we present transformations that occur frequently to illustrate the principles we
described in the previous section. We focus on transformations during the following
activities:

• Optimizing the Object Design Model

• Mapping Associations to Collections

• Mapping Contracts to Exceptions

• Mapping Object Models to a Persistent Storage Schema

1. Optimizing the Object Design Model


The direct translation of an analysis model into source code is often inefficient. The analysis
model focuses on the functionality of the system and does not take into account system
design decisions. During object design, we transform the object model to meet the design
goals identified during system design, such as minimization of response time, execution time,
or memory resources.

For example, in the case of a Web browser, it might be clearer to represent HTML
documents as aggregates of text and images. However, if we decided during system design to
display documents as they are retrieved, we may introduce a proxy object to represent
placeholders for images that have not yet been retrieved.

2. Mapping Associations to Collections


Mapping associations to collections is a technique used in object-oriented programming to
represent relationships between classes or entities. When you model associations between
objects, you typically map these associations to collections like lists, sets, or maps, depending
on the nature of the relationship. Here’s an overview of how you can map different types of
associations to collections:

1. One-to-One Association:

 Description: A one-to-one relationship means that one object is associated with exactly one
other object.
 Mapping: A one-to-one association is typically mapped to a single object or reference in
both directions (i.e., each object has a reference to the other).
 Collection: Since it’s a direct reference to one object, it’s not typically mapped to a collection
but a single reference.
 Example:
 class Person {
 Address address; // One-to-One relationship with Address
 }

 class Address {
 Person person; // One-to-One relationship with Person
 }

2. One-to-Many Association:

 Description: A one-to-many relationship means one object can be associated with multiple
objects.
 Mapping: In the case of one-to-many relationships, the “one” side holds a reference to a
collection of objects on the "many" side.
 Collection: This is typically mapped to a collection like a List, Set, or Map depending on
whether order or uniqueness of elements is important.
 Example:
 class Author {
 List<Book> books; // One-to-Many relationship with Book
 }

 class Book {
 Author author; // Many-to-One relationship with Author
 }

3. Many-to-One Association:

 Description: A many-to-one relationship means many objects are associated with one
object.
 Mapping: On the "many" side, the object holds a reference to a single object on the "one"
side.
 Collection: This is typically not mapped to a collection but as a single reference to the "one"
side object.
 Example:
 class Book {
 Author author; // Many-to-One relationship with Author
 }

 class Author {
 List<Book> books; // One-to-Many relationship with Book
 }

4. Many-to-Many Association:

 Description: A many-to-many relationship means multiple objects are associated with


multiple objects.
 Mapping: For many-to-many associations, each object in both classes holds a reference to a
collection of the other class's objects.
 Collection: This is typically mapped to a collection (such as a List, Set, or Map) of objects in
both directions.
 Example:
 class Student {
 Set<Course> courses; // Many-to-Many relationship with Course
 }

 class Course {
 Set<Student> students; // Many-to-Many relationship with Student
 }

Choosing the Right Collection:

 List: Use a List when the order of elements matters or you need to allow duplicate
elements.
 Set: Use a Set when you want to enforce uniqueness and don’t care about the order of
elements.
 Map: Use a Map when there is a key-value relationship (for example, associating an object
with a unique identifier).

Conclusion:

 One-to-One: Use a direct reference.


 One-to-Many: Use a collection (e.g., List or Set) on the "one" side.
 Many-to-One: Use a reference on the "many" side to a single object.
 Many-to-Many: Use a collection (e.g., Set) on both sides.

By correctly mapping associations to collections, you can effectively represent the


relationships between your objects in an efficient and meaningful way.

3. Mapping Object Models to a Persistent Storage Schema


Mapping object models to a persistent storage schema is an important task when working
with object-oriented systems that need to store data in a relational database or other persistent
storage formats. This process involves transforming object-oriented models (such as classes
and their relationships) into a schema that can be used to persist the data in a database. Here’s
a guide on how this mapping can be done:

1. Basic Concept:

 Object Model: This refers to the structure of objects (classes, attributes, and relationships)
used in your application.
 Persistent Storage Schema: This refers to the structure used to store data in a database
(e.g., tables, columns, and relationships between tables).

2. Types of Mappings:

There are several ways to map object models to a persistent storage schema, with the most
common being Object-Relational Mapping (ORM). The following describes the basic
techniques for mapping objects to storage schemas:

3. Mapping Classes to Tables:

 Classes to Tables: Each class in the object model typically corresponds to a table in the
relational database. The class's attributes become the columns in the table.
 Example:
 class Employee {
 int id;
 String name;
 double salary;
 }

This would map to a table like:

CREATE TABLE Employee (


id INT PRIMARY KEY,
name VARCHAR(255),
salary DECIMAL
);

4. Mapping Object Attributes to Table Columns:

 Primitive Data Types: Attributes such as int, String, double, etc., map directly to
appropriate SQL data types (e.g., INT, VARCHAR, DECIMAL).
 Complex Data Types: If a class contains references to other objects, those may require
additional tables and relationships to store the data (such as foreign keys).

5. Mapping Associations (Relationships) to Foreign Keys:

 One-to-One: A one-to-one association between classes typically results in one class


having a foreign key reference to the other. For example:
 class Address {
 String street;
 String city;
 }

 class Person {
 String name;
 Address address; // One-to-One relationship
 }

This could result in the Person table having a foreign key reference to the Address
table.

CREATE TABLE Address (


id INT PRIMARY KEY,
street VARCHAR(255),
city VARCHAR(255)
);

CREATE TABLE Person (


id INT PRIMARY KEY,
name VARCHAR(255),
address_id INT,
FOREIGN KEY (address_id) REFERENCES Address(id)
);

 One-to-Many: In a one-to-many relationship, the "many" side has a foreign key to


the "one" side. For example, in a Customer-to-Order relationship:
 class Customer {
 String name;
 }

 class Order {
 Date date;
 Customer customer; // Many Orders to One Customer
 }

The Order table would include a foreign key to the Customer table:

CREATE TABLE Customer (


id INT PRIMARY KEY,
name VARCHAR(255)
);

CREATE TABLE Order (


id INT PRIMARY KEY,
date DATE,
customer_id INT,
FOREIGN KEY (customer_id) REFERENCES Customer(id)
);

 Many-to-Many: In a many-to-many relationship, a junction table is created to hold


the foreign keys of both related tables. For example, consider a Student-to-Course
relationship:
 class Student {
 String name;
 }

 class Course {
 String title;
 }

A junction table Student_Course would store the foreign keys:

CREATE TABLE Student (


id INT PRIMARY KEY,
name VARCHAR(255)
);

CREATE TABLE Course (


id INT PRIMARY KEY,
title VARCHAR(255)
);

CREATE TABLE Student_Course (


student_id INT,
course_id INT,
PRIMARY KEY (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES Student(id),
FOREIGN KEY (course_id) REFERENCES Course(id)
);

6. Handling Inheritance:
 Single Table Inheritance: For an inheritance hierarchy, you can map all the classes
in the hierarchy to a single table, adding columns for the additional attributes of each
subclass. However, this can lead to a large table with many nullable columns.
 Example:
 class Employee {
 int id;
 String name;
 }

 class Manager extends Employee {
 String department;
 }

This could be mapped to a single table like:

CREATE TABLE Employee (


id INT PRIMARY KEY,
name VARCHAR(255),
department VARCHAR(255) -- Nullable for non-managers
);

 Class Table Inheritance: Each class in the hierarchy gets its own table, and a foreign
key references the parent class.
 Example:
 CREATE TABLE Employee (
 id INT PRIMARY KEY,
 name VARCHAR(255)
 );

 CREATE TABLE Manager (
 id INT PRIMARY KEY,
 department VARCHAR(255),
 FOREIGN KEY (id) REFERENCES Employee(id)
 );
 Concrete Table Inheritance: Each concrete subclass gets its own table with all
attributes, including those from the parent class. This eliminates the need for a foreign
key but can lead to data duplication.
 Example:
 CREATE TABLE Manager (
 id INT PRIMARY KEY,
 name VARCHAR(255),
 department VARCHAR(255)
 );

7. Handling Object References:

 Embedded Objects: In some cases, objects are embedded directly inside other objects. For
example, an Address might be embedded inside a Person object rather than having a
separate table for Address.
 Example:
 class Person {
 String name;
 Address address; // Address is embedded
 }

In this case, Person might have the Address fields directly as columns:

CREATE TABLE Person (


id INT PRIMARY KEY,
name VARCHAR(255),
street VARCHAR(255),
city VARCHAR(255)
);

8. Mapping Enumerations:

 Enums: Enum values are usually stored as strings or integers in the database. You can map
enum types to string or integer fields in your table.
 Example:
 enum Status {
 ACTIVE, INACTIVE, SUSPENDED
 }

 class User {
 Status status;
 }

This might be mapped to an integer or string in the database:

CREATE TABLE User (


id INT PRIMARY KEY,
status VARCHAR(255)
);

Conclusion:

Mapping object models to a persistent storage schema involves converting object-oriented


concepts (classes, attributes, relationships) into a relational database format (tables, columns,
foreign keys). The specific mapping strategy depends on the type of relationships (one-to-
one, one-to-many, many-to-many) and the complexity of the object model (inheritance,
embedded objects, enums). Using ORM tools (like Hibernate in Java or Entity Framework in
.NET) simplifies this process by automating much of the mapping.

 Managing Implementation

1. Documenting Transformations
Transformations enable us to improve specific aspects of the object design model and to
convert it into source code. By providing systematic recipes for recurring situations,
transformations enable us to reduce the amount of effort and the overall number of errors in
the source code. However, to retain this benefit throughout the lifetime of the system, we
need to document the application of transformations so that they can be consistently reapplied
in the event of changes to the object design model or the source code.

Reverse engineering attempts to alleviate this problem by allowing us to reconstruct the


object design model from the source code. If we could maintain a one-to-one mapping
between the source code and the object design mode, we would not need any documentation:
the tools at hand would automatically apply selected transformations and mirror changes in
the source code and the object design model. However, most useful transformations,
including those described in this chapter, are not one-to-one mappings. As a result,
information is lost in the process of applying the transformation.

Example Here’s a simplified explanation of the points you've mentioned:

1. Association Multiplicity and Collections:

 Unidirectional One-to-Many and Many-to-Many Associations:


o Both of these associations in object design often result in similar code
implementation. A one-to-many association and a many-to-many association can
end up being represented in the same way, usually using collections (like lists or
sets).
 Reverse Engineering by CASE Tools:
o When a tool reverse engineers the code, it often chooses the least restrictive
option, which is typically a many-to-many association, because it’s more flexible.
 Where Multiplicity Information is Stored:
o Information about how many objects can be associated with others (multiplicity) is
spread across different parts of the code. For example, there might be specific code
in boundary objects (the objects that interact with the outside world, such as user
interfaces) that checks for multiplicity constraints.

2. Buried Associations:

 Buried Associations in Databases:


o When one-to-many or one-to-one associations are buried in the database schema,
the relationship might not be clear. This is because, in the database schema, these
relationships may be hidden or stored in separate tables, which makes it harder to
understand the actual structure of the associations.
 Loss of Association Multiplicity Information:
o When associations are mapped to separate database tables, the multiplicity (how
many objects are associated) can get lost. This means the database schema may not
clearly indicate if it’s a one-to-one or one-to-many relationship.

3. Postconditions and Invariants:

 Postconditions and Invariants Not Mapped to Code:


o In object-oriented design, postconditions (what must be true after an operation)
and invariants (conditions that must always hold true) often aren’t directly mapped
to the code.
 Code Only Checks Preconditions:
o The system typically includes code to check preconditions (conditions that must be
true before an operation), but it often doesn’t automatically check postconditions
or invariants.
 Risk of Inconsistency:
o If postconditions or invariants are changed but not updated in the source code, the
system can become inconsistent. This means that the system’s behavior could
deviate from the expectations outlined in the object specification.

2. Assigning Responsibilities
Several roles collaborate to select, apply, and document transformations and the conversion
of the object design model into source code:

• The core architect selects the transformations to be systematically applied. For example, if
it is critical that the database schema is modifiable, the core architect decides that all
associations should be implemented as separate tables.

• The architecture liaison is responsible for documenting the contracts associated with
subsystem interfaces. When such contracts change, the architecture liaison is responsible for
notifying all class users.

• The developer is responsible for following the conventions set by the core architect and
actually applying the transformations and converting the object design model into source
code. Developers are responsible for maintaining up-to-date the source code comments with
the rest of the models

Testing

 An Overview of Testing
Reliability is a measure of success with which the observed behavior of a system conforms
to the specification of its behavior. Software reliability is the probability that a software
system will not cause system failure for a specified time under specified conditions Failure is
any deviation of the observed behavior from the specified behavior.

A fault, also called “defect” or “bug,” is the mechanical or algorithmic cause of an erroneous
state. The goal of testing is to maximize the number of discovered faults, which then allows
developers to correct them and increase the reliability of the system.

We define testing as the systematic attempt to find faults in a planned way in the
implemented software. Contrast this definition with another common one: “testing is the
process of demonstrating that faults are not present.” The distinction between these two
definitions is important.. The explicit goal of testing is to demonstrate the presence of faults
and non optimal behavior.

Figure 11-1 Testing activities and their related work products (UML activity diagram).
Swimlanes indicate who executes the test.

Figure 11-1 depicts an overview of testing activities:

• Test planning allocates resources and schedules the testing. This activity should occur
early in the development phase so that sufficient time and skill is dedicated to testing. For
example, developers can design test cases as soon as the models they validate become stable.

• Usability testing tries to find faults in the user interface design of the system. Often,
systems fail to accomplish their intended purpose simply because their users are confused by
the user interface and unwillingly introduce erroneous data.
• Unit testing tries to find faults in participating objects and/or subsystems with respect to
the use cases from the use case model.

• Integration testing is the activity of finding faults by testing individual components in


combination. Structural testing is the culmination of integration testing involving all
components of the system. Integration tests and structural tests exploit knowledge from the
SDD (System Design Document) using an integration strategy described in the Test Plan
(TP).

• System testing tests all the components together, seen as a single system to identify faults
with respect to the scenarios from the problem statement and the requirements and design
goals identified in the analysis and system design, respectively:

• Functional testing tests the requirements from the RAD and the user manual.

• Performance testing checks the nonfunctional requirements and additional design goals
from the SDD. Functional and performance testing are done by developers.

• Acceptance testing and installation testing check the system against the project agreement
and is done by the client, if necessary, with help by the developers.

 Testing Concepts
A test component is a part of the system that can be isolated for testing. A component can be
an object, a group of objects, or one or more subsystems.

• A fault, also called bug or defect, is a design or coding mistake that may cause abnormal
component behavior.

• A nerroneous state is a manifestation of a fault during the execution of the system. An


erroneous state is caused by one or more faults and can lead to a failure.

• A failure is a deviation between the specification and the actual behavior. A failure is
triggered by one or more erroneous states. Not all erroneous states trigger a failure.2

• A test case is a set of inputs and expected results that exercises a test component with the
purpose of causing failures and detecting faults.

• A test stub is a partial implementation of components on which the tested component


depends. A test driver is a partial implementation of a component that depends on the test
component. Test stubs and drivers enable components to be isolated from the rest of the
system for testing.

• A correction is a change to a component. The purpose of a correction is to repair a fault.


Note that a correction can introduce new faults.
Figure 11-2 Model elements used during testing (UML class diagram).

1. Faults, Erroneous States, and Failures

With the initial understanding of the terms from the definitions in Section 11.3, let’s take a
look at Figure 11-3. What do you see? Figure 11-3 shows a pair of tracks that are not aligned
with each other. If we envision a train running over the tracks, it would crash (fail). However,
the figure actually does not present a failure, nor an erroneous state, nor a fault. It does not
show a failure, because the expected behavior has not been specified, nor is there any
observed behavior. Figure 11-3 also does not show an erroneous state, because that would
mean that the system is in a state that further processing will lead to a failure. We only see
tracks here; no moving train is shown. To speak about erroneous state, failure, or fault, we
need to compare the desired behavior (described in the use case in the RAD) with the
observed behavior (described by the test case). Assume that we have a use case with a train
moving from the upper left track to the lower right track (Figure 11-4).

Figure 11-3 An example of a fault. The desired behavior is that the train remain on the tracks
Figure 11-4 Use case DriveTrain specifying the expected behavior of the train.

A fault in the virtual machine of a software system is another example of a mechanical fault:
even if the developers have implemented correctly, that is, they have mapped the object
model correctly onto the code, the observed behavior can still deviate from the specified
behavior. In concurrent engineering projects, for example, where hardware is developed in
parallel with software, we cannot always make the assumption that the virtual machine
executes as specified.

Other examples of mechanical faults are power failures. Note the relativity of the terms
“fault” and “failure” with respect to a particular system component: the failure in one system
component (the power system) is the mechanical fault that can lead to failure in another
system component (the software system).

2. Test Cases

A test case is a set of input data and expected results that exercises a component with the
purpose of causing failures and detecting faults. A test case has five attributes: name,
location, input, oracle, and log (Table 11-1). The name of the test case allows the tester to
distinguish between different test cases. A heuristic for naming test cases is to derive the
name from the requirement it is testing or from the component being tested. For example, if
you are testing a use case Deposit(), you might want to call the test case Test_Deposit. If a
test case involves two components A and B, a good name would be Test_AB. The location
attribute describes where the test case can be found. It should be either the path name or the
URL to the executable of the test program and its inputs.

Inputdescribes the set of input data or commands to be entered by the actor of the test case
(which can be the tester or a test driver). The expected behavior of the test case is the
sequence of output data or commands that a correct execution of the test should yield. The
expected behavior is described by the oracle attribute. The log is a set of time-stamped
correlations of the observed behavior with the expected behavior for various test runs.

3. Test Stubs and Drivers

Executing test cases on single components or combinations of components requires the tested
component to be isolated from the rest of the system. Test drivers and test stubs are used to
substitute for missing parts of the system. A test driver simulates the part of the system that
calls the component under test. A test driver passes the test inputs identified in the test case
analysis to the component and displays the results.

A test stub simulates a component that is called by the tested component. The test stub must
provide the same API as the method of the simulated component and must return a value
compliant with the return result type of the method’s type signature. Note that the interface of
all components must be baselined. If the interface of a component changes, the corresponding
test drivers and stubs must change as well.

4. Corrections

Once tests have been executed and failures have been detected, developers change the
component to eliminate the suspected faults. A correction is a change to a component whose
purpose is to repair a fault. Corrections can range from a simple modification to a single
component, to a complete redesign of a data structure or a subsystem. In all cases, the
likelihood that the developer introduces new faults into the revised component is high.
Several techniques can be used to minimize the occurrence of such faults:

Problem tracking includes the documentation of each failure, erroneous state, and fault
detected, its correction, and the revisions of the components involved in the change. Together
with configuration management, problem tracking enables developers to narrow the search
for new faults. We describe problem tracking in more detail in Chapter 13, Configuration
Management.

• Regression testing includes the reexecution of all prior tests after a change. This ensures
that functionality which worked before the correction has not been affected. Regression
testing is important in object-oriented methods, which call for an iterative development
process. This requires testing to be initiated earlier and for test suites to be maintained after
each iteration. Regression testing unfortunately is costly, especially when part of the tests is
not automated. We describe regression testing in more detail in Section 11.4.4.

• Rationale maintenance includes the documentation of the rationale for the change and its
relationship with the rationale of the revised component. Rationale maintenance enables
developers to avoid introducing new faults by inspecting the assumptions that were used to
build the component. We describe rationale maintenance in Chapter 12, Rationale
Management.

 Managing Testing
we describe the planning of test activities (Section 11.5.1). Next, we describe the test plan,
which documents the activities of testing (Section 11.5.2). Next, we describe the roles
assigned during testing (Section 11.5.3). Next, we discuss the topics of regression
testing(Section 11.5.4), automated testing (Section 11.5.5), and model-based testing (Section
11.5.6).

Planning of Test Activities:

Test Planning is a crucial phase in software testing that involves defining a strategy to
ensure the system meets all requirements and functions properly. It outlines the testing
objectives, resources, timelines, tools, and the scope of testing activities.

Key Activities in Test Planning:

1. Defining Scope and Objectives: Identify what needs to be tested, including system
components, features, and user interactions.
2. Selecting Test Types: Decide on the types of testing to be conducted, such as unit
testing, integration testing, system testing, acceptance testing, etc.
3. Resource Allocation: Determine the human resources (testers, developers) and tools
(test management tools, automation tools) required for testing.
4. Time and Schedule: Plan the timeline, including milestones and deadlines for each
phase of testing.
5. Test Environment Setup: Set up the necessary hardware, software, network
configurations, and other resources needed to run the tests.
6. Risk Management: Identify potential risks (e.g., resource shortages, environmental
issues) and develop mitigation strategies.

 Test Plan:

A Test Plan is a formal document that outlines the activities, scope, and resources for
software testing. It serves as a blueprint for the entire testing process and ensures that testing
is carried out systematically and effectively.

Key Elements of a Test Plan:

1. Introduction: Overview of the software product and its objectives.


2. Test Objectives: What the testing aims to achieve (e.g., verify functionality, ensure
performance, identify defects).
3. Scope of Testing: The features and functionalities to be tested and those that will not
be tested.
4. Test Types: Defines the types of testing (unit testing, integration testing, system
testing, etc.) that will be performed.
5. Testing Resources: The required tools, environments, hardware, and software.
6. Roles and Responsibilities: Who will perform which tasks in the testing process.
7. Test Schedule: Timeline for various testing activities.
8. Risk Management: Potential risks during testing and their mitigation plans.
9. Deliverables: Expected outcomes from testing, such as test cases, test logs, and defect
reports.
10. Exit Criteria: The conditions under which testing is considered complete (e.g., all
critical defects are fixed).
 Roles Assigned During Testing:

Various roles are assigned during the testing process, and each has its own specific
responsibilities:

1. Test Manager:
o Oversees the overall testing process.
o Responsible for test planning, resource management, and test execution
oversight.
o Ensures that the testing process meets the specified objectives and quality
standards.
2. Test Lead:
o Leads the test team and manages day-to-day testing activities.
o Reviews test cases, assigns tasks to testers, and monitors progress.
o Ensures the testing is on track and that any issues are quickly addressed.
3. Testers:
o Execute the test cases as per the test plan.
o Report defects and follow up on the resolution of issues.
o Responsible for creating, reviewing, and maintaining test cases and testing
artifacts.
4. Developers:
o Collaborate with testers by fixing defects and providing necessary technical
support.
o May assist in writing unit tests and debugging errors found during integration
or system testing.
5. Business Analysts/Subject Matter Experts (SMEs):
o Ensure that the system meets the business requirements.
o Provide input into test case creation to ensure coverage of functional aspects.
o Participate in User Acceptance Testing (UAT).
6. Automation Engineers:
o Develop and maintain automated tests.
o Focus on testing repetitive tasks or those requiring heavy load testing.
7. Configuration Manager:
o Manages the test environment and ensures that all test configurations are set
up properly.
o Responsible for managing the version control of test scripts and test data.

 Regression Testing:

Regression Testing is the process of re-running previously conducted tests to ensure that
new changes (such as bug fixes or new features) have not introduced new defects into the
system.

Key Aspects of Regression Testing:


1. Purpose: Ensure that new code changes don't negatively impact existing
functionality.
2. Scope: Can be broad (test the entire system) or narrow (test specific impacted areas).
3. Frequency: Conducted frequently, especially after each change or update to the
system.
4. Test Selection: A subset of test cases from earlier phases is selected for regression
testing based on the code changes made.

Example: If a new feature, like an online payment method, is added to an e-commerce


platform, regression testing ensures that the shopping cart and checkout processes (which are
unrelated to the new feature) still work properly.

 Automated Testing:

Automated Testing uses software tools to automatically execute tests and compare the actual
outcome with the expected result. It’s particularly useful for repetitive tests and large-scale
applications.

Key Aspects of Automated Testing:

1. Efficiency: Automated tests can be run much faster than manual tests, saving time
and resources.
2. Reusability: Test scripts can be reused across different versions of the software,
improving consistency.
3. Frequency: Automated testing is ideal for regression testing and performance testing,
where the same tests need to be run multiple times.
4. Tools: Tools like Selenium, JUnit, TestNG, and Appium are used for automating
tests.

Example: In an online banking app, automated tests can be created to check if the transfer of
funds works correctly across various accounts. These tests can be run frequently, ensuring
that no new changes break the transaction feature.

 Model-Based Testing:

Model-Based Testing (MBT) uses models (often formal or semi-formal) to represent the
system's behavior and automatically generates test cases from these models. The goal is to
identify unexpected behavior and edge cases early.

Key Aspects of Model-Based Testing:

1. Model Creation: A model that represents the expected behavior of the system (e.g.,
state diagrams, activity diagrams) is created.
2. Test Generation: From the model, automated tools generate test cases that represent
different system states and transitions.
3. Test Execution: The generated test cases are executed to validate if the system
behaves as expected.
4. Advantages:
o Can detect unexpected behaviors and edge cases that might be missed with
traditional testing.
o Helps in reducing the time spent on test case creation and maintenance.

Example: In a web application for a restaurant reservation system, a model could be built
that represents the states of reservation (e.g., booking, confirming, canceling) and the
transitions between these states. Test cases are generated automatically to ensure that the
system works correctly in all scenarios.

Summary:

1. Test Planning: Outlines the overall approach, resources, timelines, and goals for
testing.
2. Test Plan: A detailed document that specifies what, how, and when testing will occur.
3. Roles: Different roles (Test Manager, Tester, Developer, etc.) are assigned to
different tasks in the testing process.
4. Regression Testing: Re-running tests to ensure that new changes don’t break existing
functionality.
5. Automated Testing: Using tools to automate test execution for efficiency and
consistency.
6. Model-Based Testing: Using models to generate test cases, helping to identify edge
cases and reducing test creation time.

These processes together ensure that the software is tested thoroughly, efficiently, and
consistently, while reducing human error and improving software quality.

You might also like