This guideline explains how to specify components following these steps:
-
Define component interfaces
-
For each interface, specify operations and signatures
-
For each operation, specify pre- and post-conditions
1. Specify Component Interfaces
Components exchange data or use the services of other components through interfaces. Components offer
interfaces other components can use, and use the services of other
components through the interfaces those components offer. A component can offer one or more interfaces, and one or more
components can offer the same interface.
The set of all operation signatures that make up an interface, and all interfaces the component offers, characterizes
the complete set of requests that a component can fulfill. Show a component's offered and used interfaces
diagrammatically using a UML Component Specification Diagram, as shown in Figure 1. This diagram shows a single
component with the interfaces it uses, as well as the ones it offers. Initially, you may just name the interfaces, but
ultimately these interfaces will include all operations and their signatures.
Figure 1. Component Specification
Diagram
Elaborate interfaces in stages. Initially, only describe them in terms of the responsibilities that they have. Later,
refine them to reflect their full signature, using formal notation such as UML, Interface Definition Language (IDL), or
a programming language like Java™.
The specification of an interface, including its operation signatures, is a key step in fully specifying a component
model. Often, interfaces are not defined to this level of detail. This makes it difficult, if not impossible, to create
components that independent teams can work on. The additional time it takes to fully specify an interface is typically
outweighed by the time saved avoiding rework due to poorly defined components.
Why separate out interfaces from the components themselves?
-
Interfaces enforce the concept of encapsulation, or information hiding, because all access to the component's
information and behavior is through the published interface. An interface is the external view of a component
that emphasizes its abstraction, while hiding its structure and the secrets of its behavior. [BOOCH91].
-
Factoring out interfaces allows more than one component to offer that interface, even though you only define
the interface once. You can subsequently implement the interface in different ways.
Group operations that belong together in order to form a well-designed interface. If a client uses one operation of an
interface, it should use some (or even most) of the other operations, too. Sets of such operations naturally belong
together, and have related effects on the state of the component instance.
The number of interfaces and operations assigned to interfaces can vary greatly. It may be appropriate to have a single
interface with several operations or multiple interfaces, each with only a single operation. However, avoid interfaces
with too many operations (more than 12 for example), because this may indicate that the component has poor cohesion.
The notion of a component offering an interface is subtly different from
that of a component implementing an interface. An offered interface is a
logical-level concept that allows for interfaces to have both operations and associations (with data types). An
implemented interface is a physical-level concept that you handle in a later stage of development. Interface
implementation is something that a programming language may or may not support. Java, for example, supports interface
implementation, but interfaces are only allowed to have operations (and constants).
Given a set of well-structured components and their responsibilities, make these responsibilities available to other
components by defining interfaces. The approach to use depends on the nature of the component, and the layer of the
architecture in which the component resides.
-
For components in the 'Business Processing' layer, select interfaces based on the responsibilities assigned
when analyzing functional requirements (see below).
-
-
For components in the 'Middleware' and 'System Software' layers, allocate interfaces based on the type of
functionality. Components in these layers typically access data and business rules, or perform communication
functions (such as placing messages on queues). Such interfaces are generally well understood, so derive them
by examining vendor product descriptions.
When you identified components, you most likely identified controller
components with responsibilities based on the step-by-step flow of functional requirements. Factor out and group these
responsibilities as interfaces by doing the following for each component:
1. Identify external
responsibilities. Do this by analyzing the dependencies between components. Where one component depends on another
to get information or perform processing, it needs to call or send a message to that component.
2. Group like responsibilities
together. For example, group all responsibilities for getting or updating data. Group responsibilities for
performing some processing function or executing business rules separately.
3. Name an interface. The name
should reflect the function of it responsibilities.
Having grouped responsibilities, the next step is to identify the actual callable element that executes the function
implied by the responsibility. This step will also identify the data that needs to flow when the call is made.
2. Specify Operations and Signatures
For each interface, specify the operations and their signatures (that is, parameters passed and return values).
1. Look at the collaborations between components in Component Interaction
Diagrams.
2. Make decisions identifying the data needed to provide the required
functionality.
3. Create structured data types as appropriate.
Figure 2 shows a
partial Component Interaction Diagram to illustrate these steps.
Figure 2. Partial Component Interaction Diagram
Consider the operation 'listProducts' in the preceding diagram:
-
'InventoryProcessSystem' manages multiple categories of products. It needs to be passed a category number so that
it can identify the correct category.
-
Category numbers can be numbers or letters, and need to be a 'String' type.
-
A collection of 'Products' needs to be returned, so allocate it to multiple Products type.
The following signature results:
listProducts
( [in] category : ProductCategory ) : Product [*]
Repeat this process for each external operation defined for each interface.
Architects often question whether they should specify operations to this level of detail, or let the component designer
specify operation signatures. The risk of leaving the detail to component designers is that they may make decisions
that invalidate the major architectural decisions.
3. Specify Pre- and Post-Conditions
Pre- and post-conditions specify the effect of an operation without prescribing an algorithm or implementation.
-
The post-condition specifies the effect of the operation on the component's data or state, provided that the
pre-condition is true.
-
The pre-condition is the condition under which the operation guarantees that the post-condition is true.
-
If the pre-condition is false when the operation is invoked, the result is unspecified and dealt with by some
form of exception handling.
Pre- and post-conditions are kinds of assertions. An assertion is a Boolean
statement that should never be false, and therefore, will be false only if an error has occurred. Assertions are
usually only checked during build, either automatically during compilation or manually during test. They are not
typically checked during runtime execution. In fact, a program should not assume that an assertion has been checked.
Having a pre-condition makes it explicit what the caller is responsible for checking. Without this explicit statement
of responsibility, there could be too little or too much checking. Too much checking is as bad as too little because it
leads to "program bloat" and makes programs overly complex and difficult to maintain.
A pre-condition does not stop the operation from executing. This is the meaning of "Design by Contract" [MEYER97]. Some
people use the term pre-condition to describe a situation that needs to be true in order for an operation or process to
be executed. This is better termed a guard condition or the triggering event for execution [DODD05].
Pre- and post-conditions essentially form a contract between a client and a supplier. As with any good contract, it
entails obligations, as well as benefits, for both parties: with an obligation for one usually turning into a benefit
for the other.
-
The pre-condition binds the client: it defines the conditions under which a call to the supplier is legitimate.
It is an obligation for the client and a benefit for the supplier.
-
The post-condition binds the supplier: it defines the conditions that must be ensured by the operation on
return. It is a benefit for the client and an obligation for the supplier.
The
benefits are, for the client, the guarantee that certain properties will hold after the call. For the supplier, the
guarantee is that certain assumptions will be satisfied whenever the operation is called. The obligations are, for the
client, to satisfy the requirements as stated by the pre-condition, and for the supplier, to do the job as stated by
the post-condition (see Figure 3).
Figure 3. Client and supplier obligations
and benefits of pre- and post-conditions
A pre-condition violation means that the operation caller, although obligated by the contract to satisfy a certain
requirement, did not. This is a bug in the client itself: the operation is not involved. An outside observer might
criticize the contract as too demanding, but it is too late to argue over the contract. The client did not observe its
part of the deal. If there is a mechanism for monitoring assertions during execution, and it detects such a
pre-condition violation, the operation should not be executed at all. It has stated the conditions under which it can
operate, and these conditions do not hold. Trying to execute it would make no sense.
A post-condition violation means that the routine, presumably called under correct conditions, was not able to fulfill
its contract. In this case, the bug is in the routine: the caller is innocent.
Assertions such as pre- and post-conditions may or may not affect the run-time system. You can treat them as written
specifications for how the system should behave. You can test them during the various phases of testing. However, you
can also check assertions at runtime, in which case you use some form of exception handling to deal with them.
Assertion checking has an impact on performance.
Pre-
and post-conditions may be expressed either using the formal Object Constraint Language (OCL) [WARMER99], or using
natural language. Figure 4 shows an example of a pre- and post-condition using natural language.

Figure 4. Example of pre- and
post-conditions
In the example above, the operation 'cancelOrder' only guarantees to complete successfully if it receives an existing
order number and the order is active. If these two conditions are true, then 'cancelOrder' will guarantee to cancel the
order.
|