Object Oriented Design
Object Oriented Design
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:
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.
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.
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.
7
8
5.1.4 Layers and Partitions
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.
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).
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.
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.
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.
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:
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
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.
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.
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.
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.
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,
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:
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:
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:
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?
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:
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.
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.
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.
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.
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:
34
Heuristics for Selecting Design Patterns
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.
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.
36
specifying preconditions and postconditions
37