Task: Identify Design Elements
This task explains how to identify Subsystems, Classes, interfaces, Events and Signals.
Disciplines: Analysis & Design
Purpose
  • To analyze interactions of analysis classes to identify design model elements
Relationships
Main Description

The Task: Use Case Analysis results in analysis classes, which represent conceptual things which can perform behavior. In design, analysis classes evolve into a number of different kinds of design elements:

  • classes, to represent a set of rather fine-grained responsibilities;
  • subsystems, to represent a set of coarse-grained responsibilities, perhaps composed of a further set of subsystems, but ultimately a set of classes;
  • active classes, which represent threads in the system;
  • interfaces, to represent abstract declarations of responsibilities provided by a class or subsystem.

In addition, in design we shall also identify:

  • events, which are specifications of interesting occurrences in time and space that usually (if they are noteworthy) require some response from the system; and
  • signals, to represent asynchronous mechanisms used to communicate certain types of events within the system.

These finer distinctions enable us to examine different aspects of the design:

  • Events and the Signals that are used to communicate them, allow us to describe the asynchronous triggers of behavior to which the system must respond.
  • Classes and Subsystems allow us to group related responsibilities into units which can be developed in relative independence; classes fulfill an atomic set of related responsibilities, while subsystems are composite building blocks which are in turn composed of classes or other subsystems. Subsystems are used to represent the work products of a development team as a single, integral unit of functionality, and as such are used both as units of control and configuration management as well as logical design elements.
  • Active classes are used to represent control threads in the system, allowing the modeling of concurrency. Active classes are often used in composition with other classes that are usually, but not necessarily, passive: such a composition can then be used - in the same way as a collaboration - to model complex behavior.

    In real-time systems, capsules are used in place of active classes, offering stronger semantics to simplify the design and increase the reliability of concurrent applications. Capsules share some aspects of both classes and subsystems: they are in fact encapsulated collaborations of classes which together represent a thread of control in the system. They differ from subsystems in the sense that a capsule is the responsibility of a single designer, whereas a subsystem is the responsibility (typically) of a team of developers; a subsystem may contain capsules, however. 

  • Interfaces allow us to examine and capture the 'seams' of the system, defining in precise terms how the constituent parts of the system will interoperate.
  • In real-time systems, we shall use Protocols to define precisely the messages that may be sent and received on a port of a capsule.

By separating concerns and handling each issue represented by these concepts separately, we simplify the design process and clarify our solution.

If traceability is to be maintained between system models, it should be documented during this task.  For more information on documenting the traceability between the Design Model and other system models, see Guideline: Design Model.

 UML 1.x Representation

According UML 1.5, a subsystem is, effectively, a special kind of package which has only interfaces as public elements. The interfaces provide a layer of encapsulation, allowing the internal design of the subsystem to remain hidden from other model elements. The concept subsystem is used to distinguish it from "ordinary" packages, which are semantic-free containers of model elements; the subsystem represents a particular usage of packages with class-like (behavioral) properties.

In RUP, Capsules are represented using UML 1.5 notation. Much of this can be represented in UML 2.0 using the Concept: Structured Class.

Refer to Differences Between UML 1.x and UML 2.0 for more information.

Steps
Identify Events and Signals
Purpose To identify the external and internal events and signals to which the system must respond. 

Events are external and internal occurrences which cause some action within the system. Events and their characteristics can help drive the identification of key design elements, such as active classes.

An initial list of external events can be derived from the Use-Case Model, from the actors' interactions with use cases. Internal events may be derived from text in the use case flows, or may be identified as the design evolves.

Important characteristics of events are:

  • internal vs. external - Is the event external or internal?
  • priority - Does this event need to cause the suspension of other processing in order to be handled?
  • frequency - How often does the event occur?
  • frequency distribution - Does the event occur at regular intervals, or are there spikes?
  • response requirements - How the quickly the system must respond to the event (may need to distinguish between average and worst case).
  • kind - Is this a Call Event, Time Event, Signal Event, or Change Event (see Concept: Events and Signals for definitions)?

Events' characteristics should be captured as needed to drive the identification of the design elements that handle them. Capturing event characteristics tends to be most important in reactive (event-driven) systems, but it can be useful in other systems, such as those with concurrency and/or asynchronous messaging.

Asynchronous communication events can be modeled as Signals to express the data that they carry, or to express relationships between signals, such as generalization. In some systems, in particular reactive systems, it is important to relate signals received from external devices to specific mechanisms, such as interrupts or specific polling messages.

Identify Classes, Active Classes and Subsystems
Purpose To refine the analysis classes into appropriate design model elements 

Identify Classes. When the analysis class is simple and already represent a single logical abstraction, it can be directly mapped, 1:1, to a design class. Typically, entity classes survive relatively intact into Design. Since entity classes are typically also persistent, determine whether the design class should be persistent and note it accordingly in the class description.

When identifying classes, they should be grouped into Artifact: Design Packages, for organizational and configuration management purposes. See Guideline: Design Package for more information on how to make packaging decisions.

Identify Active Classes. Consider the concurrency requirements of the system in the context of the analysis objects identified: is there a need for the system to respond to externally generated events, and if so, which analysis classes are 'active' when the events occur? External events in the Use-Case Model are represented by stimuli coming from actors, interacting with a use case. Look at the corresponding Use-Case Realizations to see which objects interact when an event occurs. Start by grouping the objects together into autonomous sets of collaborating objects - these groupings represent an initial cut at a group that may form a composite active class.

If the events have important attributes that need to be captured, consider modeling them as classes, stereotyped <<signal>>. In real-time systems, these identified sets of objects should be grouped into capsules, which have strong encapsulation semantics.

The instances of active classes represent independent 'logical' threads of execution. These 'logical' threads of execution are not to be confused with or mapped literally to threads of execution in the operating system (though at some point we will map them to operating system threads of execution). Instead, they represent independent conceptual threads of execution in the solution space. Our goal in identifying them at this point in design is to be able to partition the solution into independent units based on natural 'concurrency seams' in the system. Dividing the work in this way makes the problems of dealing with concurrency conceptually simpler, since independent threads of execution can be dealt with separately except to the extent that they share underlying passive classes.

In general, an active class should be considered whenever there exist concurrency and concurrency conflicts in the problem domain. An active class should be used to represent some external concurrent object or concurrent activity within the computer. This gives us the ability to monitor and control concurrent activities.

Another natural choice is to use active classes as internal representatives of external physical devices that are connected to a computer since those physical entities are inherently concurrent. These "device driver" classes serve not only to monitor and control the corresponding physical devices but they also isolate the rest of the system from the specifics of the devices. This means that the rest of the system may not be affected even if the technology behind the devices evolves.

Another common place for using active classes is to represent logical concurrent activities. A logical activity represents a conceptual concurrent "object", such as, for example, a financial transaction or a telephone call. Despite the fact that these are not directly manifested as physical entities (although they take place in the physical world), there are often reasons to treat them as such. For instance, we may need to temporarily hold back a particular financial transaction to avoid a concurrency conflict or we may need to abort it due to failures within the system. Since these conceptual objects need to be manipulated as a unit, it is convenient to represent them as objects with interfaces of their own that provide the appropriate functional capabilities.

A particular example of this type of conceptual object is an active object controller. Its purpose is to continuously manage one or more other active objects. This normally involves bringing each object into the desired operational state, maintaining it in that state in the face of various disruptions such as partial failures, and synchronizing its operation with the operation of other objects. These active object controllers often evolve from Control objects identified during Task: Use-Case Analysis.

Because of their capacity to simply and elegantly resolve concurrency conflicts, active classes are also useful as guardians of shared resources. In this case, one or more resources that are required by multiple concurrent activities are encapsulated within an active class. By virtue of their built-in mutual exclusion semantics, such guardians automatically protect these resources against concurrency conflicts.

For real-time systems, capsules should be used in place of active classes: wherever you identified the need for an active class according to the heuristics described above, a capsule should be substituted.

Identify Subsystems. When the analysis class is complex, such that it appears to embody behaviors that cannot be the responsibility of a single class acting alone, the analysis class should be mapped to a design subsystem. The design subsystem is used to encapsulate these collaborations in such a way that clients of the subsystem can be completely unaware of the internal design of the subsystem, even as they use the services provided by the subsystem.

A subsystem is modeled as a UML component, which has only interfaces as public elements. The interfaces provide a layer of encapsulation, allowing the internal design of the subsystem to remain hidden from other model elements. The concept subsystem is used to distinguish it from packages, which are semantic-free containers of model elements.

The decision to create a subsystem from a set of collaborating analysis classes is based largely on whether the collaboration can be or will be developed independently by a separate design team. If the collaborations can be completely contained within a package along with the collaborating classes, a subsystem can provide a stronger form of encapsulation than that provided by a simple package. The contents and collaborations within a subsystem are completely isolated behind one or more interfaces, so that the client of the subsystem is only dependent upon the interface. The designer of the subsystem is then completely isolated from external dependencies; the designer (or design team) is required to specify how the interface is realized, but they are completely free to change the internal subsystem design without affecting external dependencies. In large systems with largely independent teams, this degree of de-coupling combined with the architectural enforcement provided by formal interfaces is a strong argument for the choice of subsystems over simple packages. See Guideline: Design Subsystem for more information about the factors which affect the choice to use subsystems as design elements.

Identify Subsystem Interfaces
Purpose To identify the design elements which formalize the seams in the system. 

Interfaces define a set of operations which are realized by some classifier. In the Design Model, interfaces are principally used to define the interfaces for subsystems. This is not to say that they cannot be used for classes as well, but for a single class it is usually sufficient to define public operations on the class which, in effect, define its 'interface'. Interfaces are important for subsystems because they allow the separation of the declaration of behavior (the interface) from the realization of behavior (the specific classes within the subsystem which realize the interface). This de-coupling provides us with a way to increase the independence of development teams working on different parts of the system, while retaining precise definitions of the 'contracts' between these different parts.

For each subsystem, identify a set of candidate interfaces. Using the grouped collaborations identified in the previous step, identify the responsibility which is 'activated' when the collaboration is initiated. This responsibility is then refined by determining what information must be provided by the 'client' and what information is returned when the collaboration is complete; these sets of information become the prototype input and output parameters and return value for an operation which the subsystem will realize. Define a name for this operation, using the naming conventions defined in the Artifact: Project-Specific Guidelines. Repeat this until all operations which will be realized by the subsystem have been defined.

Next, group operations together according to their related responsibilities. Smaller groups are preferable to larger groups, since it is more likely that a cohesive set of common responsibilities will exist if there are fewer operations in the group. Keep an eye toward reuse as well - look for similarities that may make it easier to identify related reusable functionality. At the same time, though, don't spend a great deal of time trying to find the ideal grouping of responsibilities; remember, this is just a first-cut grouping and refinement will proceed iteratively throughout the elaboration phase.

Look for similarities between interfaces. From the candidate set of interfaces, look for similar names, similar responsibilities, and similar operations. Where the same operations exist in several interfaces, re-factor the interfaces, extracting the common operations into a new interface. Be sure to look at existing interfaces as well, re-using them where possible. The goal is to maintain the cohesiveness of the interfaces while removing redundant operations between interfaces. This will make the interfaces easier to understand and evolve over time.

Define interface dependencies. The parameters and return value of each interface operation each have a particular type: they must realize a particular interface, or they must be instances of a simple data type. In cases where the parameters are objects that realize a particular interface, define dependency relationships between the interface and the interfaces on which it depends. Defining the dependencies between interfaces provides useful coupling information to the software architect, since interface dependencies define the primary dependencies between elements in the design model.

Map the interfaces to subsystems. Once interfaces have been identified, create realization associations between the subsystem and the interfaces it realizes. A realization from the subsystem to an interface indicates that there are one or more elements within the subsystem that realize the operations of the interface. Later, when the subsystem is designed, these subsystem-interface realizations will be refined, with the subsystem designer specifying which specific elements within the subsystem realize the operations of the interface. These refined realizations are visible only to the subsystem designer; from the perspective of the subsystem client, only the subsystem-interface realization is visible.

Define the behavior specified by the interfaces. Interfaces often define an implicit state machine for the elements that realize the interface. If the operations on the interface must be invoked in a particular order (e.g. the database connection must be opened before it can be used), a state machine that illustrates the publicly visible (or inferred) states that any design element that realizes the interface must support should be defined. This state machine will aid the user of the interface to better understand the interface, and will aid the designer of elements which realize the interface to provide the correct behavior for their element.

Package the interfaces. Interfaces are owned by the software architect; changes to interfaces are always architecturally significant. To manage this, the interfaces should be grouped into one or more packages owned by the software architect. If each interface is realized by a single subsystem, the interfaces can be placed in the same package with the subsystem. If the interfaces are realized by more than one subsystem, they should be placed within a separate package owned by the software architect. This allows the interfaces to be managed and controlled independently of the subsystems themselves.

Identify Capsule Protocols

Purpose

To identify the design elements which formalize the seams in the system (RT design only).

Protocols are similar to interfaces in event-driven systems: they identify the 'contract' between capsules by defining a matched set of signals which are used to communicate between independent threads of control. While interfaces are primarily used to define synchronous messaging using a function call model of invocation, protocols are primarily used to define asynchronous communication using signal-based messaging. Protocols allow the separation of the declaration of behavior (the set of signals) from the realization of behavior (the elements within the subsystem which realize the interface). This de-coupling provides us with a way to increase the independence of development teams working on different parts of the system, while retaining precise definitions of the 'contracts' between these different parts.

For each capsule, identify a set of in and out signals. Using the grouped collaborations identified in earlier steps, identify the responsibility which is 'activated' when the collaboration is initiated. This responsibility is then refined by determining what information must be provided by the 'client' and what information is returned when the collaboration is complete; these sets of information become the prototype input parameters for a signal which the capsule will realize through one of its ports. Define a name for this signal, using the naming conventions defined in the Artifact: Project-Specific Guidelines. Repeat this until all signals which will be realized by the capsule have been defined.

Next, group signals together according to their related responsibilities. Smaller groups are preferable to larger groups, since it is more likely that a cohesive set of common responsibilities will exist if there are fewer signals in the group. Keep an eye toward reuse as well - look for similarities that may make it easier to identify related reusable functionality. At the same time, though, don't spend a great deal of time trying to find the ideal grouping of responsibilities; remember, this is just a first-cut grouping and refinement will proceed iteratively throughout the elaboration phase. Give the protocol a meaningful name, one that describes the role the protocol plays in capsule collaborations.

Look for similarities between protocols. From the candidate set of protocols, look for similar names, similar responsibilities, and similar signals. Where the same signals exist in several protocols, re-factor the protocols, extracting the common signals into a new interface. Be sure to look at existing protocols as well, re-using them where possible. The goal is to maintain the cohesiveness of the protocols while removing redundant signals between protocols. This will make the protocols easier to understand and evolve over time.

Map the protocols to capsules. Once protocols have been identified, create ports on the capsules which realize the protocols. The ports of the capsule define its 'interfaces', the behavior that can be requested from the capsule. Later, when the capsule is designed, the behavior specified by the ports will be described by the state machine for the capsule.

Define the behavior specified by the protocols. Protocols often define an implicit state machine for the elements that realize the interface. If the input signals on the interface must be received in a particular order (e.g. a 'system-ready' signal must be received before a particular error signal can be received), a state machine that illustrates the publicly visible (or inferred) states that any design element that realizes the protocol must support should be defined. This state machine will aid the user of the capsules which realize the protocol to better understand their behavior, and will aid the designer of capsules to provide the correct behavior for their element.

Package the protocols. Protocols are owned by the software architect; changes to protocols are always architecturally significant. To manage this, the protocols should be grouped into one or more packages owned by the software architect. This allows the protocols to be managed and controlled independently of the capsules which realize the protocols.


More Information