By finding Analysis Classes, you outline the key elements in a system. These elements evolve
quickly, and remain fluid as you explore different ways to incorporate them into your architecture. Formal documentation
can impede this process, so be careful how much energy you expend on maintaining this model in a formal sense; you can
waste a lot of time polishing a model that is largely expendable. Analysis classes rarely survive in the architecture
unchanged. Many of them represent whole collaborations of objects, often encapsulated by subsystems.
Finding boundary classes
A boundary class remediates the interface to something outside the system. Boundary classes insulate the system from
changes in the surroundings (changes in interfaces to other systems, changes in user requirements, and so on), keeping
these changes from affecting the rest of the system. Typical types of boundary classes include user interface, system
interface, and device interface elements.
Model boundary classes according to what kind of boundary they represent. Communication with another system and
communication with a user (through a user interface) have very different objectives. For communication with a user, the
most important concern is how the system presents the interface to the user. For communication with another system, the
most important concern is the communication protocol.
Find user interface classes
Define one boundary class for each "conversation" between the system and a user. This class has the responsibility for
coordinating the interaction with the user. You may also define subsidiary boundary classes, to which the primary
boundary class delegates some of its responsibilities. This is particularly true for window-based GUI applications,
where you may model one boundary class for each window or form. Only model the key abstractions of the system; do not
model every button, list, and widget in the GUI. The goal of using analysis classes is to form a good picture of how
the system is composed, not to capture every last detail.
Find system interface classes
A boundary class that communicates with an external system is responsible for managing the exchange of information with
the external system: it provides the interface to that system.
For example, in an Automated Teller Machine, withdrawal of funds must be verified through the ATM Network, a system
that verifies the withdrawal with the bank accounting system. A class called ATM Network Interface captures all of the
behavior related to communication with the ATM Network.
The interface to an existing system may already be well-defined. If so, derive the responsibilities directly from the
interface definition. If a formal interface definition exists, you do not need to formally define it here. Simply make
note that the existing interface will be reused.
Find device interface classes
The system may contain external devices (such as sensor equipment) that change value spontaneously without any object
in the system affecting them. You need to consider the source for all external events, and make sure that you have a
way for the system to detect these events.
For each device, create a boundary class to capture its responsibilities. If there is a well-defined interface already
existing for the device, make note of it for later reference.
Finding control classes
Control classes provide coordinating behavior in the system. Complex requirements generally require one or more control
classes to coordinate the behavior of other objects in the system. Requirements that involve only the simple
manipulation of stored information may not require control objects: they can just use entity and boundary
objects.
Use control classes to model behavior that:
-
Is independent of surroundings (does not change when the surroundings change)
-
Defines control logic (order between events)
-
Changes little if the internal structure or behavior of entity classes change
-
Uses or sets the contents of several entity classes, and therefore needs to coordinate their behavior
-
Is not performed in the same way every time that it is activated
Start by identifying one control class for each scenario of system use.
Determine whether you need a control class
For each flow of system events, start by investigating if the flow can be handled by the already identified boundary
and entity classes. For simple flows of events that primarily enter, retrieve and display, or modify information, a
separate control class is not usually justified; the boundary classes should be responsible for coordinating
behavior.
Use a separate control class when behavior is complex and dynamic, and may change independently from the interfaces
(boundary classes) or information stores (entity classes). By encapsulating a particular flow of events, you can
potentially reuse the same control class for a variety of systems that may have different interfaces and different
information stores.
Divide complex classes along lines of similar responsibilities
Consider a requirement to manage a queue of tasks. You may identify a control class to handle the queue of tasks,
ensuring that tasks execute in the right order. It performs the next task in the queue as soon as suitable resources
are available. The system can therefore perform several tasks at the same time.
This control object is easier to describe if you split it into two control classes:
-
A Queue Handler object handles only the queue order and the allocation of transportation
equipment. One Queue Handler object manages the whole queue. As soon as the system needs to perform a task, it
creates a new Task Performer object.
-
A Task Performer object performs tasks. One Task Performer object handles each task the system
performs.
The principal benefit of this split is to separate queue handling responsibilities (something generic to many
circumstances) from the specific tasks of task management, which are specific to this scenario. This makes the classes
easier to understand, and easier to adapt as the system matures.
Separate the main flow of events from alternative or exceptional flows of events
To simplify changes, encapsulate the main flow of events and alternative flows of events in different control classes.
If alternative and exception flows are completely independent, separate them from each other as well. This makes the
system easier to extend and maintain over time.
Divide control classes where two actors share the same control class
Control classes may also need to be divided when several actors (elements outside the system that interact with the
system) use the same control class. By doing this, you isolate changes in the requirements of one actor from the rest
of the system. In cases where the cost of change is high, identify all of the control classes related to more than one
actor and divide them. In the ideal case, each control class should interact (via some boundary object) with one actor
or none at all.
You do not have to divide a control class if:
-
You can be reasonably sure that the behavior of the actors related to the control class will never change, or
change very little.
-
The behavior of the control class toward one actor is very insignificant compared with its behavior toward another
actor. In this case, a single class can hold all of the behavior. Combining behavior in this way will have a
negligible effect on maintainability.
For example, consider the requirement to handle local phone calls in a telephone system.
In a local phone call, there are two actors:
-
A-subscriber initiates the call. This subscriber lifts the receiver, hears the dial tone, and then
dials a number of digits, which the system stores and analyzes. When the system has received all of the digits, it
sends a ringing tone to A-subscriber, and a ringing signal to B-subscriber.
-
B-subscriber receives the call. When B-subscriber hears the ringing tone and answers the call, the
tone and the signal stop, and the conversation between the subscribers can begin.
The call ends when both subscribers hang up.
Initially, you may identify a control class to manage the call itself. However, two behaviors must be controlled: what
happens on A-subscriber's end and what happens on B-subscriber's end. For this reason, split the original control
object into two control objects, A-behavior and B-behavior.
Finding entity classes
Entity classes represent stores of information in the system. They are typically used to represent the key concepts
that the system manages. Entity objects are frequently passive and persistent. Their main responsibilities are to store
and manage information in the system.
If your requirements have a glossary or a business-domain model, use this as a source of inspiration for entity
classes.
If the concept you wish to model is not used by any other class, you can model it as an attribute of an entity class,
or even as a relationship between entity classes. If any other class uses it, model it as a class.
Enforcing consistency
When you identify a new behavior, check to see if an existing class has similar responsibilities, reusing classes where
possible. Only create new classes when you are sure that no existing object can perform the behavior.
As you identify classes, examine them to ensure that they have consistent responsibilities. When class responsibilities
are disjoint, split the object into two or more classes.
A class with only one responsibility is not a problem, but it should raise questions about why you need it. Challenge
and justify the existence of all classes.
|