0% found this document useful (0 votes)
233 views37 pages

Object Oriented Design

The document discusses system design and object oriented design. It describes system design as decomposing an analysis model into smaller subsystems that can be realized by individual teams. Key aspects of system design include defining design goals, decomposing the system, and selecting strategies for building the system. The result is a model with a subsystem decomposition and descriptions of the selected strategies.

Uploaded by

Ganesh Thapa
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)
233 views37 pages

Object Oriented Design

The document discusses system design and object oriented design. It describes system design as decomposing an analysis model into smaller subsystems that can be realized by individual teams. Key aspects of system design include defining design goals, decomposing the system, and selecting strategies for building the system. The result is a model with a subsystem decomposition and descriptions of the selected strategies.

Uploaded by

Ganesh Thapa
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/ 37

Unit 5: Object Oriented Design

System design is the transformation of an analysis model into a system design


model. During system design, developers define the design goals of the project
and decompose the system into smaller subsystems that can be realized by
individual teams. Developers also select strategies for building the system, such
as the hardware/software strategy, the persistent data management strategy, the
global control flow, the access control policy, and the handling of boundary
conditions. The result of system design is a model that includes a subsystem
decomposition and a clear description of each of these strategies.
System design is decomposed into several activities, each addressing part of the
overall problem of decomposing the system:

Identify design goals. Developers identify and prioritize the qualities of


the system that they should optimize.

Design the initial subsystem decomposition. Developers decompose the


system into smaller parts based on the use case and analysis models.
Developers use standard architectural styles as a starting point during
this activity.

Refine the subsystem decomposition to address the design goals. The


initial decomposition usually does not satisfy all design goals.
Developers refine it until all goals are satisfied.
Analysis results in the requirements model described by the following
products:

 a set of nonfunctional requirements and constraints, such as maximum


response time, minimum throughput, reliability, operating system
platform, and so on
 a use case model, describing the system functionality from the actors’
point of view
 an object model, describing the entities manipulated by the system
 a sequence diagram for each use case, showing the sequence of
interactions among objects participating in the use case.
The analysis model describes the system completely from the actors’ point of
view and serves as the basis of communication between the client and the

1
developers. The analysis model, however, does not contain information about
the internal structure of the system, its hardware configuration, or more
generally, how the system should be realized. System design is the first step in
this direction. System design results in the following products:

 design goals, describing the qualities of the system that developers


should optimize
 software architecture, describing the subsystem decomposition in
terms of subsystem responsibilities, dependencies among subsystems,
subsystem mapping to hardware, and major policy decisions such as
control flow, access control, and data storage
 boundary use cases, describing the system configuration, startup,
shutdown, and exception handling issues.
The design goals are derived from the nonfunctional requirements. Design
goals guide the decisions to be made by the developers when trade-offs are
needed. The subsystem decomposition constitutes the bulk of system design.
Developers divide the system into manageable pieces to deal with complexity:
each subsystem is assigned to a team and realized independently.

2
5.1 System Design Concepts
A service is a set of related operations that share a common purpose. During
system design, we define the subsystems in terms of the services they provide.
Later, during object design, we define the subsystem interface in terms of the
operations it provides. Next, we look at two properties of subsystems, coupling
and cohesion . Coupling measures the dependencies between two subsystems,
whereas cohesion measures the dependencies among classes within a
subsystem. Ideal subsystem decomposition should minimize coupling and
maximize cohesion.
Then, we look at layering and partitioning, two techniques for relating
subsystems to each other . Layering allows a system to be organized as a
hierarchy of subsystems, each providing higher-level services to the subsystem
above it by using lower-level services from the subsystems below it.
Partitioning organizes subsystems as peers that mutually provide different
services to each other.

5.1.1 Subsystems and Classes


In order to reduce the complexity of the application domain, we identified
smaller parts called “classes” and organized them into packages. Similarly, to
reduce the complexity of the solution domain, we decompose a system into
simpler parts, called “subsystems,” which are made of a number of solution
domain classes. A subsystem is a replaceable part of the system with well-
defined interfaces that encapsulates the state and behavior of its contained
classes.
By decomposing the system into relatively independent subsystems, concurrent
teams can work on individual subsystems with minimal communication
overhead. In the case of complex subsystems, we recursively apply this
principle and decompose a subsystem into simpler subsystems.

For example, the accident management system we previously described can be


decomposed into a DispatcherInterface subsystem, realizing the user interface for

3
the Dispatcher; a FieldOfficerInterface subsystem, realizing the user interface for the
FieldOfficer; an IncidentManagement subsystem, responsible for the creation, modification, and
storage of Incidents; a ResourceManagement subsystem, responsible for tracking available
Resources (e.g., FireTrucks and Ambulances); a MapManagement for depicting Maps and
Locations; and a Notification subsystem, implementing the communication between
FieldOfficer terminals and Dispatcher stations.

This subsystem decomposition is depicted in Figure 6-4 using UML


components. Components are depicted as rectangles with the component icon
in the upper right corner. Dependencies among components can be depicted
with dashed stick arrows. In UML, components can represent both logical and
physical components.
5.1.2 Services and Subsystem Interfaces
A subsystem is characterized by the services it provides to other subsystems. A
service is a set of related operations that share a common purpose. A
subsystem providing a notification service, for example, defines operations to
send notices, look up notification channels, and subscribe and unsubscribe to a
channel. The set of operations of a subsystem that are available to other
subsystems form the subsystem interface. The subsystem interface includes
the name of the operations, their parameters, their types, and their return
values. System design focuses on defining the services provided by each
subsystem, that is, enumerating the operations, their parameters, and their high-
level behavior. Object design will focus on the application programmer
4
interface (API), which refines and extends the subsystem interfaces. The API
also includes the type of the parameters and the return value of each operation.

Provided and required interfaces can be depicted in UML with assembly


connectors, also called ball-and-socket connectors. The provided interface is
shown as a ball icon (also called lollipop) with its name next to it. A required
interface is shown as a socket icon. The dependency between two subsystems is
shown by connecting the corresponding ball and socket in the component
diagram.

Figure 6-5 depicts the dependencies among the FieldOfficerInterface,


DispatchterInterface and ResourceManagement subsystems. The FieldOfficerInterface
requires the ResourceUpdateService to update the status and location of the FieldOfficer. The
DispatcherInterface requires the ResourceAllocationService to identify available resources and
allocating them to new Incidents. The ResourceManagement subsystem provides both services.
Note that we use the ball-and-socket notation when the subsystem decomposition is already fairly
stable and that our focus has shifted from the identification of subsystems to the definition of
services.

The definition of a subsystem in terms of the services it provides helps us focus


on its interface as opposed to its implementation. When writing a subsystem
interface, one should strive to minimize the amount of information provided
about the implementation.

5.1.3 Coupling and Cohesion


Coupling is the number of dependencies between two subsystems. If two
subsystems are loosely coupled, they are relatively independent, so
5
modifications to one of the subsystems will have little impact on the other. If
two subsystems are strongly coupled, modifications to one subsystem is likely
to have impact on the other. A desirable property of a subsystem decomposition
is that subsystems are as loosely coupled as reasonable. This minimizes the
impact that errors or future changes in one subsystem have on other
subsystems.
Consider, for example, the emergency response system depicted in Figure
6-4. During system design, we decide to store all persistent data (i.e., all data
that outlive a single execution of the system) in a relational database. This leads
to an additional subsystem called Database (Figure 6-6). Initially, we design the
interface of the database subsystem so that subsystems that need to store data
simply issue commands in the native query language of the database, such as
SQL. For example, the IncidentManagement subsystem issues SQL queries to store
and retrieve records representing Incidents in the database. This leads to a
situation with a high coupling among the Database subsystem and the three client
subsystems (i.e., IncidentManagement, ResourceManagement, and MapManagement) that need to
store and retrieve data, as any change in the way the data is stored will require changes in the client
subsystems. For example, if we change database vendors we will have to change the subsystems to
use a different dialect of the query language. To reduce the coupling among these four subsystems,
we decide to create a new subsystem, called Storage, which shields the Database from the other
subsystems. In this alternative, the three client subsystems use services provided by the Storage
subsystem, which is then responsible for issuing queries in SQL to the Database subsystem. If we
decide to change database vendors or to use a different storage mechanism (e.g., flat files), we only
need to change the Storage subsystem. Hence, the overall coupling of the subsystem decomposition
has been decreased.

Note that reducing coupling is not an end in itself. In the example above,
reducing the coupling resulted in additional complexity. By reducing coupling,
developers can introduce many unnecessary layers of abstraction that consume
development time and processing time. High coupling is an issue only if it is
likely that any subsystem changes.

Cohesion is the number of dependencies within a subsystem. If a subsystem


contains many objects that are related to each other and perform similar tasks,
its cohesion is high. If a subsystem contains a number of unrelated objects, its
cohesion is low. A desirable property of a subsystem decomposition is that it
leads to subsystems with high cohesion.

For example, consider a decision tracking system for recording design


problems, discussions, alternative evaluations, decisions, and their
6
implementation in terms of tasks (Figure 6-7). DesignProblem and Option represent
the exploration of the design space: we formulate the system in terms of a
number of DesignProblems and document each Option they explore. The Criterion
class represents the qualities in which we are interested. Once we assessed the
explored Options against desirable Criteria, we implement Decisions in terms of
Tasks. Tasks are recursively decomposed into Subtasks small enough to be assigned to
individual developers. We call atomic tasks ActionItems.

7
8
5.1.4 Layers and Partitions

A hierarchical decomposition of a system yields an ordered set of layers. A


layer is a grouping of subsystems providing related services, possibly realized
using services from another layer. Layers are ordered in that each layer can
depend only on lower level layers and has no knowledge of the layers above it.
The layer that does not depend on any other layer is called the bottom layer,
and the layer that is not used by any other is called the top layer (Figure 6-9).
In a closed architecture, each layer can access only the layer immediately
1
below it. In an open architecture, a layer can also access layers at deeper
levels.

9
Closed, layered architectures have desirable properties: they lead to low
coupling between subsystems, and subsystems can be integrated and tested
incrementally. Each level, however, introduces a speed and storage overhead
that may make it difficult to meet nonfunctional requirements. Also, adding
functionality to the system in later revisions may prove difficult, especially
when the additions were not anticipated. In practice, a system is rarely
decomposed into more than three to five layers.

Another approach to dealing with complexity is to partition the system into


peer subsystems, each responsible for a different class of services. For
example, an onboard system for a car could be decomposed into a travel
service that provides real-time directions to the driver, an individual
preferences service that remembers a driver’s seat position and favorite radio
station, and vehicle service that tracks the car’s gas consumption, repairs, and
scheduled maintenance. Each subsystem depends loosely on the others, but can
often operate in isolation.
In general, a subsystem decomposition is the result of both partitioning and
layering. We first partition the system into top-level subsystems, which are
responsible for specific functionality or run on a specific hardware node. Each
of the resulting subsystems are, if complexity justifies it, decomposed into
lower- and lower-level layers until they are simple enough to be implemented
by a single developer.
5.1.5 Architectural Styles

As the complexity of systems increases, the specification of system


decomposition is critical. It is difficult to modify or correct weak
decomposition once development has started, as most subsystem interfaces
would have to change. In recognition of the importance of this problem, the
concept of software architecture has emerged. A software architecture
includes system decomposition, global control flow, handling of boundary
conditions, and intersubsystem communication protocols.
Several architectural styles that can be used as a basis for the architecture of
different systems are

Repository

10
In the repository architectural style (see Figure 6-13), subsystems access
and modify a single data structure called the central repository. Subsystems
are relatively independent and interact only through the repository. Control
flow can be dictated either by the central repository (e.g., triggers on the data
invoke peripheral systems) or by the subsystems (e.g., independent flow of
control and synchronization through locks in the repository).

Repositories are typically used for database management systems, such as a


payroll system or a bank system. Repositories are well suited for applications
with constantly changing, complex data-processing tasks.

Model/View/Controller
In the Model/View/Controller (MVC) architectural style (Figure 6-15),
subsystems are classified into three different types: model subsystems maintain
domain knowledge, view subsystems display it to the user, and controller
subsystems manage the sequence of interactions with the user. The model
subsystems are developed such that they do not depend on any view or
controller subsystem. Changes in their state are propagated to the view
subsystem via a subscribe/notify protocol. The MVC is a special case of the
repository where Model implements the central data structure and control objects
dictate the control flow.

11
Client/server
In the client/server architectural style (Figure 6-18), a subsystem, the server,
provides services to instances of other subsystems called the clients, which are
responsible for interacting with the user. The request for a service is usually
done via a remote procedure call mechanism or a common object broker (e.g.,
CORBA, Java RMI, or HTTP). Control flow in the clients and the servers is
independent except for synchronization to manage requests or to receive
results. An information system with a central database is an example of a
client/server architectural style. The clients are responsible for receiving inputs
from the user, performing range checks, and initiating database transactions
when all necessary data are collected.

Peer-to-peer
peer-to-peer architectural style (see Figure 6-20) is a generalization of
the client/ server architectural style in which subsystems can act both as client
or as servers, in the sense that each subsystem can request and provide services.

12
The control flow within each subsystem is independent from the others except
for synchronizations on requests.

An example of a peer-to-peer architectural style is a database that both


accepts requests from the application and notifies to the application whenever
certain data are changed

Three-tier
The three-tier architectural style organizes subsystems into three layers
(Figure 6-22):
The interface layer includes all boundary objects that deal with the user,
including windows, forms, web pages, and so on.
The application logic layer includes all control and entity objects,
realizing the processing, rule checking, and notification required by the
application.
The storage layer realizes the storage, retrieval, and query of persistent
objects.

13
Four-tier
The four-tier architectural style is a three-tier architecture in which the
Interface layer is decomposed into a Presentation Client layer and a Presentation
Server layer (Figure 6-23). The Presentation Client layer is located on the user
machines, whereas the Presentation Server layer can be located on one or more servers.
The four-tier architecture enables a wide range of different presentation clients in the application,
while reusing some of the presentation objects across clients. For example, a banking information
system can include a host of different clients, such as a Web browser interface for home users, an
Automated Teller Machine, and an application client for bank employees. Forms shared by all three
clients can then be defined and processed in the Presentation Server layer, thus removing
redundancy across clients.

Pipe and filter


In the pipe and filter architectural style (Figure 6-24), subsystems process data
received from a set of inputs and send results to other subsystems via a set of
outputs. The subsystems are called “filters,” and the associations between the
subsystems are called “pipes.” Each filter knows only the content and the
format of the data received on the input pipes, not the filters that produced
them. Each filter is executed concurrently, and synchronization is accomplished
via the pipes. The pipe and filter architectural style is modifiable: filters can be
substituted for others or reconfigured to achieve a different purpose.

14
15
5.2 System Design Activities:

System design consists of transforming the analysis model into the design
model that takes into account the nonfunctional requirements described in the
requirements analysis document.
Review Analysis Model
Identifying Design Goals :

The definition of design goals is the first step of system design. It identifies the
qualities that our system should focus on. Many design goals can be inferred
from the nonfunctional requirements or from the application domain. Others
will have to be elicited from the client. It is, however, necessary to state them
explicitly such that every important design decision can be made consistently
following the same set of criteria.
In general, we can select design goals from a long list of highly desirable
qualities. These criteria are organized into five groups: performance,
dependability, cost, maintenance, and end user criteria. Performance,
dependability, and end user criteria are usually specified in the requirements or
inferred from the application domain. Cost and maintenance criteria are
dictated by the customer and the supplier.
Identifying Subsystems
Finding subsystems during system design is similar to finding objects during
analysis. The initial subsystem decomposition should be derived from the
functional requirements. Another heuristic for subsystem identification is to
keep functionally related objects together. A starting point is to assign the
participating objects that have been identified in each use case to the
subsystems.
Heuristics for grouping objects into subsystems

Assign objects identified in one use case into the same subsystem.
Create a dedicated subsystem for objects used for moving data among subsystems.
Minimize the number of associations crossing subsystem boundaries.
All objects in the same subsystem should be functionally related.

Subsystem decomposition reduces the complexity of the solution domain by


minimizing coupling among subsystems.

16
5.3 An Overview of System Design Activities
During system design, we identify design goals, decompose the system into
subsystems, and refine the subsystem decomposition until all design goals are
addressed.
Design goals guide the decisions to be made by the developers especially when
trade-offs are needed. Developers divide the system into manageable pieces to
deal with complexity: each subsystem is assigned to a team and realized
independently. . In particular, they need to address the following issues:

Hardware/software mapping: What is the hardware configuration of the


system? Which node is responsible for which functionality?
Data management: Which data should be persistent? Where should persistent
data be stored? How are they accessed?
Access control: Who can access which data? Can access control change
dynamically?
Control flow: How does the system sequence operations? Is the system event
driven? Can it handle more than one user interaction at a time?
Boundary conditions: How is the system initialized and shut down? How are
exceptional cases handled?
Figure 7-1 depicts the activities of system design. Each activity addresses one
of the issues we described above.

17
5.4 System Design Activities: Addressing Design Goals

Addressing design Goals ensure that subsystem decomposition addresses all the
nonfunctional requirements and can account for any constraints during the
implementation phase. Here, we refine the subsystem decomposition by

 Mapping Subsystems to Processors and Components (Section 7.4.1)


 Identifying and Storing Persistent Data (Section 7.4.2)
 Providing Access Control (Section 7.4.3)
 Designing the Global Control Flow (Section 7.4.4)
 Identifying Services (Section 7.4.5)
 Identifying Boundary Conditions (Section 7.4.6)
 Reviewing the System Design Model (Section 7.4.7).

5.4.1 Mapping Subsystems to Processors and Components

Selecting a hardware configuration and a platform : Many systems run


on more than one computer and depend on access to an intranet or to the
18
Internet. The use of multiple computers can address high-performance
needs and interconnect multiple distributed users. Consequently, we need
to examine carefully the allocation of subsystems to computers and the
design of the infrastructure for supporting communication between
subsystems. These computers are modeled as nodes in UML deployment
diagrams. Because the hardware mapping activity has significant impact
on the performance and complexity of the system, we perform it early in
system design. Selecting a hardware configuration also includes selecting
a virtual machine onto which the system should be built. The virtual
machine includes the operating system and any software components that
are needed, such as a database management system or a communication
package. The selection of a virtual machine reduces the distance between
the system and the hardware platform on which it will run.
Allocating objects and subsystems to nodes : Once the hardware
configuration has been defined and the virtual machines selected, objects
and subsystems are assigned to nodes. This often triggers the
identification of new objects and subsystems for transporting data among
the nodes.

In general, allocating subsystems to hardware nodes enables us to


distribute functionality and processing power where it is most needed.
Unfortunately, it also introduces issues related to storing, transferring,
replicating, and synchronizing data among subsystems. For this reason,
developers also select the components they will use for developing the system.

5.4.2 Identifying and Storing Persistent Data


Persistent data outlive a single execution of the system. For example, at the
end of the day, an author saves his work into a file on a word processor. The
file can then be reopened later. The word processor need not run for the file to
exist. Where and how data is stored in the system affects system
decomposition. In some cases, for example, in a repository architectural style
(see Section 6.3.5), a subsystem can be completely dedicated to the storage of
data. The selection of a specific database management system can also have
implications on the overall control strategy and concurrency management.

Identifying persistent objects


First, we identify which data must be persistent. The entity objects identified
during analysis are obvious candidates for persistency. In general, we can
identify persistent objects by examining all the classes that must survive system

19
shutdown, either in case of a controlled shutdown or an unexpected crash. The
system will then restore these long-lived objects by retrieving their attributes
from storage during system initialization or on demand as the persistent objects
are needed.

Selecting a storage management strategy


Once all persistent objects are identified, we need to decide how these
objects should be stored. In general, there are currently three options for
storage management:

Flat files. Files are the storage abstractions provided by operating systems.
The application stores its data as a sequence of bytes and defines how and
when data should be retrieved.

Relational database. A relational database provides data abstraction at a


higher level than flat files. Data are stored in tables that comply with a
predefined type called a schema.

Object-oriented database. An object-oriented database provides services


similar to a relational database. Unlike a relational database, it stores data as
objects and associations.

20
5.4.3 Providing Access Control
In multi-user systems, different actors have access to different
functionality and data. For example, an everyday actor may only access the
data it creates, whereas a system administrator actor may have unlimited access
to system data and to other users’ data. During analysis, we modeled these
distinctions by associating different use cases to different actors. During system
design, we model access by determining which objects are shared among
actors, and by defining how actors can control access. Depending on the
security requirements of the system, we also define how actors are
authenticated to the system (i.e., how actors prove to the system who they are)
and how selected data in the system should be encrypted. We can represent the
access matrix using one of three different approaches: global access table,
access control list, and capabilities.

A global access table represents explicitly every cell in the matrix as a


(actor,class, operation) tuple. Determining if an actor has access to a specific
object requires looking up the corresponding tuple. If no such tuple is found,
access is denied.

21
An access control list associates a list of (actor,operation) pairs with
each class to be accessed. Empty cells are discarded. Every time an object is
accessed, its access list is checked for the corresponding actor and operation. .
An example of an access control list is the guest list for a party. A butler checks
the arriving guests by comparing their names against names on the guest list. If
there is a match, the guests can enter; otherwise, they are turned away.

A capability associates a (class,operation) pair with an actor. A


capability allows an actor access to an object of the class described in the
capability. Denying a capability is equivalent to denying access. An example of
a capability is an invitation card for a party. In this case, the butler checks if the
arriving guests hold an invitation for the party. If the invitation is valid, the
guests are admitted; otherwise, they are turned away. No other checks are
necessary.

An access matrix only represents static access control. This means that
access rights can be modeled as attributes of the objects of the system. In the
bank information system example,

consider a broker actor who is assigned a set of portfolios. By policy, a broker


cannot access portfolios managed by another broker. In this case, we need to
model access rights dynamically in the system, and, hence, this type of access
is called dynamic access control.

In both static and dynamic access control, we assume that we know the actor:
either the user behind the keyboard or the calling subsystem. This process of
verifying the association between the identity of the user or subsystem and the
system is called authentication. A widely used authentication mechanism, for
example, is for the user to specify a user name, known by everybody, and a
corresponding password, only known to the system and stored in an access
control list. The system protects its users’ passwords by encrypting them before
storing or transmitting them.

22
5.4.4 Designing the Global Control Flow
Control flow is the sequencing of actions in a system. In object-oriented systems, sequencing
actions includes deciding which operations should be executed and in which order. These
decisions are based on external events generated by an actor or on the passage of time.

Control flow is a design problem. During analysis control flow is not an issue, because we
assume that all objects are running simultaneously executing operations any time they need to.
There are three possible control flow mechanisms:
Procedure-driven control. Operations wait for input whenever they need data from an actor.
This kind of control flow is mostly used in legacy systems and systems written in procedural
languages. It introduces difficulties when used with object-oriented languages. As the
sequencing of operations is distributed among a large set of objects, it becomes increasingly
difficult to determine the order of inputs by looking at the code.
Event-driven control. A main loop waits for an external event. Whenever an event becomes
available, it is dispatched to the appropriate object, based on information associated with the
event. This kind of control flow has the advantage of leading to a simpler structure and to
centralizing all input in the main loop.
Threads. Threads are the concurrent variation of procedure-driven control: The system can
create an arbitrary number of threads, each responding to a different event. If a thread needs
additional data, it waits for input from a specific actor. This kind of control flow is the most
intuitive of the three mechanisms.
Once a control flow mechanism is selected, we can realize it with a set of one or more control
objects. The role of control objects is to record external events, store temporary states about
them, and issue the right sequence of operation calls on the boundary and entity objects
associated with the external event.

23
5.4.5 Identifying Services

In this activity, we refine the subsystem decomposition by identifying the services provided by
each subsystems. We review each dependency between subsystems and define an interface for
each service we identified (depicted in UML by a lollipop). In this activity, we name the
identified services. During object design, we specify each service precisely in terms of
operations, parameters, and constraints. By focusing on dependencies between subsystems, we
refine the subsystem responsibilities, we find omissions in our decomposition, and we validate
the current software architecture.

For example, let us focus on the interfaces of the CommunicationSubsystem of MyTrip. The
responsibility of the CommunicationSubsystem is to transport trips from the PlanningSubsystem
to the RoutingSubsystem. The RoutingSubsystem initiates the connection, as the
PlanningSubsystem is a server that is always available, while the RoutingSubsystem runs only
while the car is powered. This asymmetry leads us to define three interfaces (Figure 7-14):
ConnectionManager allows a subsystem to register with the CommunicationSubsystem,
to authenticate, find other nodes, and initiate and close connections.
TripRequester allows a subsystem to request a list of available trips and download
selected trips.
TripProvider allows a subsystem to provide a list of trips that are available for the
specified car driver and respond to specific trip requests.

24
5.4.6 Identifying Boundary Conditions

We still need to examine the boundary conditions of the system—that is, to decide how the
system is started, initialized, and shut down—and we need to define how we deal with major
failures such as data corruption and network outages, whether they are caused by a software error
or a power outage. Uses cases dealing with these conditions are called boundary use cases.

It is common that boundary use cases are not specified during analysis or that they are
treated separately from the common use cases. In general, we identify boundary use cases by
examining each subsystem and each persistent object:

Configuration. For each persistent object, we examine in which use cases it is created or
destroyed (or archived).
Start-up and shutdown. For each component (e.g., a WebServer), we add three use cases to start,
shutdown, and configure the component.
Exception handling. For each type of component failure (e.g., network outage), we decide how
the system should react (e.g., inform users of the failure).
In general, an exception is an event or error that occurs during the execution of the system.
Exceptions are caused by three different sources:

A hardware failure. Hardware ages and fails.

Changes in the operating environment. The environment also affects the way a system
works.

A software fault. An error can occur because the system or one of its components contains a
design error.

Exception handling is the mechanism by which a system treats an exception. In the case of a
user error, the system should display a meaningful error message to the user so that she can
correct her input.

25
5.4.7 Reviewing System Design
Like analysis, system design is an evolutionary and iterative activity.
In addition to meeting the design goals that were identified during system design, we need to
ensure that the system design model is correct, complete, consistent, realistic, and readable. The
system design model is correct if the analysis model can be mapped to the system design model.
You should ask the following questions to determine if the system design is correct:

 Can every subsystem be traced back to a use case or a nonfunctional requirement?


 Can every use case be mapped to a set of subsystems?
 Can every design goal be traced back to a nonfunctional requirement?
 Is every nonfunctional requirement addressed in the system design model?
 Does each actor have an access policy?
 Is every access policy consistent with the nonfunctional security requirement?

The model is complete if every requirement and every system design issue has been addressed.
You should ask the following questions to determine if the system design is complete:

 Have the boundary conditions been handled?


 Was there a walkthrough of the use cases to identify missing functionality in the system
design?
 Have all use cases been examined and assigned a control object?
 Have all aspects of system design (i.e., hardware allocation, persistent storage, access
control, legacy code, boundary conditions) been addressed?
 Do all subsystems have definitions?

26
The model is consistent if it does not contain any contradictions. You should ask the following
questions to determine if a system design is consistent:
 Are conflicting design goals prioritized?
 Does any design goal violate a nonfunctional requirement?
 Are there multiple subsystems or classes with the same name?
 Are collections of objects exchanged among subsystems in a consistent manner?

The model is realistic if the corresponding system can be implemented. You ask the following
questions to determine if a system design is realistic:

 Are any new technologies or components included in the system? Was the
appropriateness or robustness of these technologies or components evaluated? How?
 Have performance and reliability requirements been reviewed in the context of subsystem
decomposition?

 Have concurrency issues (e.g., contention, deadlocks) been addressed?

The model is readable if developers not involved in the system design can understand the
model. You should ask the following questions to ensure that the system design is readable:

 Are subsystem names understandable?


 Do entities (e.g., subsystems, classes) with similar names denote similar concepts?
 Are all entities described at the same level of detail?

27
5.5 Object Design: Reusing Pattern
During system design, we describe the system in terms of its architecture, such as its subsystem
decomposition, global control flow, and persistency management. During system design, we also
define the hardware/software platform on which we build the system. This allows the selection
of off-the-shelf components that provide a higher level of abstraction than the hardware. 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.

Conceptually, software system development fills the gap between a given problem and an
existing machine. Analysis reduces the gap between the problem and the machine by identifying
objects representing problem-specific concepts. 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.

Object design includes four groups of activities


Reuse. Off-the-shelf components identified during system design are used to help in the
realization of each subsystem. Class libraries and additional components are selected for basic
data structures and services. Design patterns are selected for solving common problems and for
protecting specific classes from future change.
Interface specification. During this activity, the subsystem services identified during system
design are specified in terms of class interfaces, including operations, arguments, type signatures,
and exceptions. Additional operations and objects needed to transfer data among subsystems are
also identified. The result of service specification is a complete interface specification for each
subsystem.
Restructuring. Restructuring activities manipulate the system model to increase code reuse or
meet other design goals. Each restructuring activity can be seen as a graph transformation on
subsets of a particular model.
Optimization. Optimization activities address performance requirements of the system
model. This includes changing algorithms to respond to speed or memory requirements, reducing

28
multiplicities in associations to speed up queries, adding redundant associations for efficiency,
rearranging execution orders, adding derived attributes to improve the access time to objects, and

opening up the architecture, that is, adding access to lower layers because of performance
requirements.

Object design is not sequential. Although each group of activities described above addresses a
specific object design issue, they usually occur concurrently. Usually, interface specification and
reuse activities occur first, yielding an object design model that is then checked against the use
cases that exercise the specific subsystem. Restructuring and optimization activities occur next,
once the object design model for the subsystem is relatively stable.

29
5.6 Reuse Concepts: Solution Objects, Inheritance, and Design Patterns
5.6.1 Application Objects and Solution Objects
Class diagrams can be used to model both the application domain and the solution domain.
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.

30
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.
5.6.2 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.
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.
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. Conversely, the classification of concepts into type
hierarchies is called specification inheritance.
5.6.3 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:

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.

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.

31
Delegation is a preferable mechanism to implementation inheritance as it does not interfere with
existing components and leads to more robust code.

5.6.4 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.
Liskov Substitution Principle

If an object of type S can be substituted in all the places where an object of type T is expected, then S is a subtype of T.

32
In object design, the Liskov Substitution Principle means that if all classes are subtypes of their superclasses, all inheritance
relationships are specification inheritance relationships. In other words, a method written in terms of a superclass T must be
able to use instances of any subclass of T without knowing whether the instances are of a subclass. Consequently, new
subclasses of T can be added without modifying the methods of T, hence leading to an extensible system. An inheritance
relationship that complies with the Liskov Substitution Principle is called strict inheritance.

5.6.5 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 [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. The Adapter design pattern (Figure 8-5; Appendix
A.2) is a template solution for such problems.

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.

5.7 Reuse Activities: Selecting Design Patterns and Components

System design and object design introduce a strange paradox in the development process. 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

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

5.7.1 Encapsulating Data Stores with the Bridge Pattern


5.7.2 Encapsulating Legacy Components with the Adapter Pattern
5.7.3 Encapsulating Context with the Strategy Pattern

34
Heuristics for Selecting Design Patterns

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.

5.8 Managing Reuse


Reuse, whether design patterns, frameworks, or components, has many technical and managerial
advantages:
Lower development effort. When reusing a solution or a component, many standard
errors are avoided. Moreover, in the case of design patterns, the resulting system is more
easily extended and more resilient to typical changes. This results in less development
effort and reduces the need for human resources, which can be redirected to testing the
software to ensure better quality.
Lower risk. When reusing repetitively the same design pattern or component, the typical
problems that will be encountered are known and can be anticipated. Moreover, the time
needed to adapt the design pattern or to glue the component is also known, resulting in a
more predictable development process and fewer risks.

Widespread use of standard terms. The reuse of a standard set of design patterns and
components fosters the use of a standard vocabulary. For example, terms such as
Adapter, Bridge, Command, or Facade denote precise concepts that all developers
become familiar with. This reduces the number of different terms and solutions to
common problems and reduces misunderstandings among developers.

Increased reliability. Reuse by itself does not increase reliability or reduce the need for
testing. However, a culture of reuse in a software organization can increase reliability for
all of the above reasons: reduced development time can lead to an increased testing
effort, repetitive use of components can lead to a knowledge base of typical problems to
be anticipated, and use of standard terms reduces communication failures.

The main challenges include:


NIH (Not Invented Here) syndrome. Since software engineering education (at
least until recently) emphasizes mostly the design of new solutions, developers often
distrust the reuse of existing solutions, especially when the customization of the solution
under consideration is limited or constrained.
Process support. The processes associated with identifying, reusing, and
customizing an existing solution are different than those involved in creating a brand new
solution.

Training. Given the lack of knowledge support tools for reuse, training is the single most
effective method in establishing a reuse culture.

35
Assigning Responsibilities
Individual developers assigned to subsystems will not spontaneously turn to design patterns
and components unless they have experience with these topics. To foster a reuse culture, an
organization needs to make the incentives of reuse as high as possible for the individual
developer. This includes access to expert developers who can provide advice and information,
and specific components or patterns, training, and emphasis on reuse during design reviews and
code inspections.
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.

5.9 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 focus of interface specification is for developers to communicate clearly and precisely about
increasingly lower-level details of the system.

The interface specification activities of object design include

 identifying missing attributes and operations


 specifying type signatures and visibility
 specifying invariants

36
 specifying preconditions and postconditions

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.

37

You might also like