MODELING COMPONENT-LEVEL DESIGN
1. What Is a Component?
Component-level design occurs after the first iteration of
architectural design has been completed. At this stage, the
overall data and program structure of the software has been
established. The intent is to translate the design model into
operational software.
Component-level design bridges the gap between architectural
design and coding.
A component is a modular building block for computer software.
More formally, the OMG Unified Modeling Language Specification
defines a component as “a modular, deployable, and
replaceable part of a system that encapsulates
implementation and exposes a set of interfaces.”
a) An Object-Oriented View
In the context of object-oriented software engineering, a
component contains a set of collaborating classes.
Each class within a component has been fully elaborated to
include all attributes and operations that are relevant to its
implementation.
As part of the design elaboration, all interfaces that enable the
classes to communicate and collaborate with other design
classes must also be defined.
To accomplish this, you begin with the analysis model and
elaborate analysis classes (for components that relate to the
problem domain) and infrastructure classes (for components that
provide support services for the problem domain).
To illustrate this process of design elaboration, consider software
to be built for a sophisticated print shop.
The overall intent of the software is to collect the customer’s
requirements at the front counter, cost a print job, and then pass
the job on to an automated production facility.
During requirements engineering, an analysis class called
PrintJob was derived.
The attributes and operations defined during analysis are
noted in the diagram.
During architectural design, PrintJob is defined as a component
within the software architecture and is represented using the
shorthand UML notation.
Note that PrintJob has two interfaces, computeJob, which
provides job costing capability, and initiateJob, which passes
the job along to the production facility.
These are represented using the “lollipop” symbols shown to the
left of the component box.
Component-level design begins at this point.
The details of the component Print- Job must be elaborated to
provide sufficient information to guide implementation.
The original analysis class is elaborated to flesh out all attributes
and operations required to implement the class as the
component PrintJob.
ELABORATION OF A DESIGN COMPONENT
b) The Traditional View
In the context of traditional software engineering, a component is
a functional element of a program that incorporates processing
logic, the internal data structures that are required to implement
the processing logic, and an interface that enables the
component to be invoked and data to be passed to it.
A traditional component, also called a module, resides within the
software architecture and serves one of three important roles:
i. A control component that coordinates the invocation of all
other problem domain components,
ii. A problem domain component that implements a complete or
partial function that is required by the customer, or
iii. An infrastructure component that is responsible for functions
that support the processing required in the problem domain.
To illustrate this process of design elaboration for traditional
components, again consider software to be built for the print shop.
A hierarchical architecture is derived and shown in figure.
Each box represents a software component.
Note that the shaded boxes are equivalent in function to the
operations defined for the PrintJob.
In this case, however, each operation is represented as a
separate module that is invoked as shown in the figure.
Other modules are used to control processing and are therefore
control components.
STRUCTURE CHART FOR A TRADITIONAL SYSTEM
To illustrate this process, consider the module
ComputePageCost.
The intent of this module is to compute the printing cost per
page based on specifications provided by the customer.
Data required to perform this function are:
àNumber of pages in the document,
àTotal number of documents to be produced,
àOne- or two-side printing,
àColor requirements, and
àSize requirements.
These data are passed to ComputePageCost via the module’s
interface. ComputePageCost uses these data to determine a
page cost that is based on the size and complexity of the job
—a function of all data passed to the module via the interface.
Page cost is inversely proportional to the size of the job and
directly proportional to the complexity of the job.
The ComputePageCost module accesses data by invoking the
module get-JobData, which allows all relevant data to be passed
to the component, and a database
Interface, accessCostsDB, which enables the module to access
a database that contains all printing costs.
As design continues, the ComputePageCost module is
elaborated to provide algorithm detail and interface detail in
figure.
Algorithm detail can be represented using the pseudocode text
shown in the figure or with a UML activity diagram.
The interfaces are represented as a collection of input and output
data objects or items.
Design elaboration continues until sufficient detail is provided to
guide construction of the component.
However, don’t forget the architecture that must house the
components or the global data structures that may serve many
components.
COMPONENT-LEVEL DESIGN FOR COMPUTEPAGECOST
c) Process Related View
Over the past four decades, the software engineering community
has emphasized the need to build systems that make use of existing
software components or design patterns. To do this, a catalog of
proven design or code-level components needs to be made
available to you as design work proceeds.
2. Designing Class-Based Components
Ans:
Basic Design Principles
Four basic design principles are applicable to component-
level design and have been widely adopted when object-oriented
software engineering is applied.
i. The Open-Closed Principle (OCP).
“A module [component] should be open for extension but
closed for modification”
For example, assume that the SafeHome security function
makes use of a Detector class that must check the status of
each type of security sensor.
It is likely that as time passes, the number and types of
security sensors will grow.
If internal processing logic is implemented as a sequence of if-
then-else constructs, each addressing a different sensor type,
the addition of a new sensor type will require additional internal
processing logic (still another if-then-else). This is a violation of
OCP.
One way to accomplish OCP for the Detector class is shown in
diagram.
The sensor interface presents a consistent view of sensors to
the detector component.
If a new type of sensor is added, no change is required for the
Detector class (component). The OCP is preserved.
Following the OCP
ii. The Liskov Substitution Principle (LSP).
“Subclasses should be substitutable for their base classes”
Example:
• The Liskov Substitution Principle in practical software development.
The principle defines that objects of a superclass shall be
replaceable with objects of its subclasses without breaking the
application. That requires the objects of your subclasses to behave
in the same way as the objects of your superclass.
iii. The Dependency Inversion Principle (DIP).
“Depend on abstractions. Do not depend on concretions”
High-level modules should not import anything from low-level
modules. Both should depend on abstractions (e.g., interfaces).
Abstractions should not depend on details.
iv. The Interface Segregation Principle (ISP).
“A Client should not be forced to implement an interface that it
doesn’t use.”
In the field of software engineering, the interface segregation principle
(ISP) states that no code should be forced to depend on methods it
does not use.
The following is an image that follows the ISP:
• Interface for customer
v. The Reuse/Release Equivalency Principle (REP).
“The granule of reuse is the granule of release”
• When classes or components are designed for reuse, an
implicit contract is established between the developer of the
reusable entity and the people who will use it.
• The developer commits to establish a release control system that
supports and maintains older versions of the entity while the users
slowly upgrade to the most current version.
• Rather than addressing each class individually, it is often advisable
to group reusable classes into packages that can be managed and
controlled as newer versions evolve. Designing components for
reuse requires more than good technical design. It also requires
effective configuration control mechanisms.
vi. The Common Closure Principle (CCP).
“Classes that change together belong together”
vii. The Common Reuse Principle (CRP).
“Classes that aren’t reused together should not be grouped together”
3. Conducting Component-Level Design
Step 1. Identify all design classes that correspond to the
problem domain.
• Using the requirements and architectural model, each analysis
class and architectural component is elaborated
Step 2. Identify all design classes that correspond to the
infrastructure domain.
• These classes are not described in the requirements model and
are often missing from the architecture model, but they must be
described at this point.
Step 3. Elaborate all design classes that are not acquired as
reusable components.
• Elaboration requires that all interfaces, attributes, and
operations necessary to implement the class be described
in detail.
Step 3a. Specify message details when classes or components
collaborate.
• The requirements model makes use of a collaboration diagram
to show how analysis classes collaborate with one another.
Figure illustrates:
• A simple collaboration diagram for the printing system
discussed earlier.
• Three objects, ProductionJob, WorkOrder, and JobQueue,
collaborate to prepare a print job for submission to the
production stream.
• Messages are passed between objects as illustrated by the
arrows in the figure.
• During requirements modeling the messages are specified as
shown in the figure. However, as design proceeds, each
message is elaborated by expanding its syntax in the following
manner
[guard condition] sequence expression (return value) :=
message name (argument list)
• where a [guard condition] is written in Object Constraint
Language and specifies any set of conditions that must be
met before the message can be sent;
• sequence expression is an integer value that indicates the
sequential order in which a message is sent; (return value) is
the name of the information that is returned by the operation
invoked by the message;
• Message name identifies the operation that is to be invoked;
and (argument list) is the list of attributes that are passed to the
operation.
Collaboration diagram with messaging
• Step 3b. Identify appropriate interfaces for each component.
• Step 3c. Elaborate attributes and define data types and data
structures required to implement them.
In general, data structures and types used to define attributes are
defined within the context of the programming language that is to be
used for implementation. UML defines an attribute’s data type using
the following syntax:
name: type-expression = initial-value {property-string}
• Where name is the attribute name, type expression is the data type,
initial value is the value that the attribute takes when an object is
created, and property-string defines a property or characteristic of
the attribute.
Refactoring interfaces and class definitions for PrintJob
• The attribute list for PrintJob lists only the names of the
attributes. However, as design elaboration proceeds, each
attribute is defined using the UML attribute format noted. For
example, paperType-weight is defined in the following manner:
paperType-weight: string = “A” {contains 1 of 4 values – A,
B, C, or D}
which defines paperType-weight as a string variable initialized
to the value A that can take on one of four values from the set
{A, B, C, D}.
Step 3d. Describe processing flow within each operation in
detail.
• This may be accomplished using a programming language-based
pseudocode or with a UML activity diagram.
For example, the operation compute- PaperCost()
computePaperCost (weight, size, color): numeric
This indicates that computePaperCost() requires the
attributes weight, size, and color as input and returns a value
that is numeric (actually a dollar value) as output.
Step 4. Describe persistent data sources (databases and files)
and identify
the classes required to manage them.
Databases and files normally transcend the design description of an
individual component.
Step 5. Develop and elaborate behavioral representations for a
class or component.
UML state diagrams were used as part of the requirements model to
represent the externally observable behavior of the system and the
more localized behavior of individual analysis classes.
UML activity diagram forcomputePaperCost()
Statechart fragment for PrintJob class
The transition from one state (represented by a rectangle with rounded
corners) to another occurs following an event that takes the form of:
Event-name (parameter-list) [guard-condition] / action expression
where event-name identifies the event, parameter-list incorporates data
that are associated with the event, guard-condition is written in Object
Constraint Language (OCL) and specifies a condition that must be met
before the event can occur, and action expression defines an action that
occurs as the transition takes place.
• Each state may define entry/ and exit/ actions that occur as
transition into the state occurs and as transition out of the state
occurs, respectively.
• In most cases, these actions correspond to operations that are
relevant to the class that is being modeled.
• The do/ indicator provides a mechanism for indicating activities that
occur while in the state, and the include/ indicator provides a means
for elaborating the behavior by embedding more statechart detail
within the definition of a state.
Step 6. Elaborate deployment diagrams to provide additional
implementation detail.
Deployment diagrams are used as part of architectural design and
are represented in descriptor form.
Step 7. Refactor every component-level design representation
and always consider alternatives.
The first component-level model you create will not be as complete,
consistent, or accurate as the nth iteration you apply to the model. It is
essential to refactor as design work is conducted.