Rất mong mọi ngưởi júp e. E dang rất cần:
Release Planning
We start this process by proposing a sequence of releases, each of which builds on
the previous release.
■ Develop a minimal functionality release, which monitors just one sensor.
■ Complete the sensor hierarchy.
■ Complete the classes responsible for managing the display.
■ Complete the classes responsible for managing the user interface.
We could order these releases in just about any manner, but we choose this one,
which progresses from highest to lowest risk, thereby forcing our development
process to directly attack the hard problems first.
Developing the minimal functionality release forces us to take a vertical slice
through our architecture and implement small parts of just about every key
abstraction. This activity addresses the highest risk in the project, namely, whether we have the right abstractions with the right roles and responsibilities.
This activity also gives us early feedback because we can now play with an exe-
cutable system. Forcing early closure like this has a number of technical and
social benefits. On the technical side, it forces us to begin to bolt the hardware
and software parts of our system together, thereby identifying any impedance
mismatches early. On the social side, it allows us to get early feedback about the
look and feel of the system, from the perspectives of real users.
Because completing this release is largely a manner of tactical implementation,
we will not bother with exposing any more of its structure. We will now turn to
elements of later releases because they reveal some interesting insights about the
development process.

The Sensor Mechanism
In inventing the architecture for this system, we have already seen how we had to
iteratively and incrementally evolve our abstraction of the sensor classes, which
we began during analysis. In this evolutionary release, we expect to build on the
earlier completion of a minimal functional system and finish the details of this
class hierarchy.
At this point in our development cycle, the class hierarchy we first presented
in Figure 11–4 remains stable, although, not surprisingly, we had to adjust the
location of certain polymorphic operations in order to extract greater commonal-
ity. Specifically, in an earlier section we noted the requirement for the
currentValue operation, declared in the abstract base class Sensor. We
may complete our design of the Sensor class (Figure 11–20).
Notice that through the class constructor, we gave the instances of this class
knowledge of their name and ID. This is essentially a kind of runtime type identi-
fication, but providing this information is unavoidable here because, per the
requirements, each sensor instance must have a mapping to a particular interface.
We can hide the secrets of this mapping by making this interface a function of a
sensor name and ID

Now that we have added this new responsibility, we can go back and simplify the
signature of DisplayManager::display to take only a single argument,
namely, a reference to a Sensor object. We can eliminate the other arguments to
this function because the Display Manager can now ask the Sensor object
its name and ID.
Making this change is advisable because it simplifies certain cross-class inter-
faces. Indeed, if we fail to keep up with small, rippling changes such as this one,
our architecture will eventually suffer as the protocols among collaborating
classes become inconsistently applied.
The declaration of the immediate subclass Calibrating Sensor builds on
the base class Sensor (Figure 11–21).
Calibrating Sensor introduces two new operations (setHighValue and
setLowValue) and implements the previously defined function currentValue.
Next, consider the declaration of the subclass Historical Sensor, which
builds on the class Calibrating Sensor (Figure 11–22).
Historical Sensor has four operations whose implementation requires col-
laboration with the TimeDate class for the time of the high or low values. Note
that Historical Sensor is still an abstract class because we have not yet
completed the definition of the abstract function rawValue, which we defer to
be a concrete subclass responsibility.


The class Trend Sensor inherits from Historical Sensor and adds one
new responsibility (Figure 11–23).
Trend Sensor introduces one new function. As with some of the other opera
tions that some other intermediate classes have added, we declare trend as con
crete because we do not desire that subclasses change their behavior.
Ultimately, we reach concrete subclasses such as Temperature Sensor (Fig
ure 11–24).
Notice that the signature of this class’s constructor is slightly different than its
superclass’s, simply because at this level of abstraction, we know the specific
name of the class. Also, notice that we have introduced the operation current-
Temperature, which follows from our earlier analysis. This operation is
semantically the same as the polymorphic function currentValue, but we
choose to include both of them because the operation currentTemperature
is slightly more type-safe.
Once we have successfully completed the implementation of all classes in this
hierarchy and integrated them with the previous release, we may proceed to the
next level of the system’s functionality.

The Display Mechanism
Implementing the next release, which completes the functionality of the classes
DisplayManager and LCD Device, requires virtually no new design work,
just some tactical decisions about the signature and semantics of certain functions,
Combining the decisions we made during analysis with our first architec-
tural prototype, wherein we made some important decisions about the protocol
for displaying sensor values, we can derive the concrete interface shown in
Figure 11–25.
None of these operations are abstract because we neither expect nor desire any
subclasses.
Notice that this class exports several primitive operations (such as
displayTime and refresh) but also exposes the composite operation
display, whose presence greatly simplifies the action of clients that must inter-
act with instances of Display Manager.
Display Manager ultimately uses the resources of the LCD Device class,
which, as we described earlier, serves as a skin over the underlying hardware. In
this manner, Display Manager raises our level of abstraction by providing a
protocol that speaks more directly to the nature of the problem space.


The User Interface Mechanism
The focus of our last major release is the tactical design and implementation of
the classes Keypad and InputManager. Similar to the LCD Device class,
the Keypad class serves as a skin over the underlying hardware, which thereby
relieves the InputManager of the nasty details of talking directly to the hard-
ware. Decoupling these two abstractions also makes it far easier to replace the
physical input device without destabilizing our architecture

We start with a declaration that names the physical keys in the vocabulary of ou
problem space. An enumeration class, Key, is defined as shown in Figure 11–26
We use the k prefix to avoid name clashes with literals defined in SensorName
Continuing, we may capture our abstraction of the Keypad class as shown in
Figure 11–27.
The protocol of this class derives from our earlier analysis. We have added the
operation inputPending so that clients can query if user input exists that has
not yet been processed.
The class InputManager has a similarly sparse interface (Figure 11–28).

As we will see, most of the interesting work of this class is carried out in
implementation of its finite state machine.
As we illustrated earlier in Figure 11–14, instances of the Sampler,
InputManager, and Keypad classes collaborate to respond to user inp
integrate these three abstractions, we must subtly modify the interface of
Sampler class to include a new object, repInputManager (Figure 1
Through this design decision, we establish an association among instance
Sensors, Display Manager, and InputManager classes at the ti
construct an instance of Sampler. This design asserts that instances of Sa
must always have a collection of sensors, a display manager, and an input
manager.
We must also incrementally modify the implementation of the function


Here we have added an invocation to processKeyPress at the beginning of
every time frame.
The processKeyPress operation is the entry point to the finite state machine
that drives the instances of this class. Ultimately, there are two approaches we can
take to implement this or any other finite state machine: We can explicitly repre-
sent states as objects (and thereby depend on their polymorphic behavior), or we
can use enumeration literals to denote each distinct state.
For modest-sized finite state machines such as the one embodied by the
InputManager class, it is sufficient for us to use the latter approach. Thus, we
might first introduce the names of the class’s outermost states (Figure 11–30).
Next, we introduce some protected helper functions (Figure 11–31).
Finally, we can begin to implement the state transitions we first introduced in Fig-ure 11–12.


The implementation of this function and its associated helper functions thus par-
allels the state transition diagram shown in Figure 11–12.