28 Design and Documentation: 2: Decomposition, I.E. Breaking A Problem Into Smaller Subparts That Can Be Dealt With
28 Design and Documentation: 2: Decomposition, I.E. Breaking A Problem Into Smaller Subparts That Can Be Dealt With
• decomposition, i.e. breaking a problem into smaller subparts that can be dealt with
largely independently;
and
• iteration, a problem gets worked through many times at increasing levels of detail;
provisional decisions get made, are tested through prototyping, and subsequently
may be revised.
Sometimes, the things that an object must do are complex (e.g. the Manager object's
application of the scheduling rules in the Supermarket example). In such cases, you can
adopt a "top-down functional decomposition approach" because you are in the same
situation as before – there is one clearly defined function to perform and the data are
relatively simple (the data needed will all be represented as data members of the object
performing the action).
You can code and test a class that defines any one type of data and the related
functionality. Then you can return to the whole problem. But now the problem has
been simplified because some details are now packaged away into the already built
component.
When you return to the whole problem, you try to characterize the workings
program in terms of interactions amongst example objects: e.g. "the structure will ask
each part in its components list to draw itself".
If you design using top-down functional decomposition, you tend to see each
problem as unique. If take an object based approach, you are more likely to see
commonalities.
As just noted, most object-based programs seem to have "composite" structures that Opportunity for reuse
group separate components. The mechanisms for "grouping" are independent of the
specific application. Consequently, you can almost always find opportunities for
reusing standard components like lists, dynamic arrays, priority queues. You don't need
to create a special purpose storage structure; you reuse a standard class.
An object-based approach to design has two major benefits: i) a cleaner, more
effective decomposition of a complex problem, and ii) the opportunity to reuse
components.
The use of abstract classes and inheritance, "object oriented design", brings further
benefits. These have been hinted at in the Supermarket example, and in the discussion
above regarding the CAD program and the parts that it manipulates. These design
benefits are explored a little more in Part V.
You start by thinking about prototypical objects, not the classes and certainly not
abstract class hierarchies.
Identifying Some ideas for the prototypical objects can come from a detailed reading of the full
prototypical objects specification of the program (the "underline the nouns" approach). As previously
noted, there are problems with too literally underlining the nouns; you may end
modelling the world in too much detail. But it is a starting point for getting ideas as to
things that might be among the more important objects – thus, you can pick up the need
for Customers and Checkouts from the description of the Supermarket problem.
Usually, you will find that the program has a few objects that seem to be in for the
duration, e.g. the UserInteraction and CardCollection objects in the RefCards
program, or the Shop , Manager, and Door objects in the Supermarket example. In
addition there are other objects that may be transient (e.g. the Customers). An
important aspect of the design will be keeping track of when objects get created and
destroyed.
Scenarios-1 Once you have formed at least some idea as to the objects that might be present in
the executing program, it is worthwhile focussing on "events" that the program deals
with and the objects that are involved. This process helps clarify what each kind of
object owns and does, and also begins to establish the patterns of communication
amongst classes.
You make up scenarios for each of the important "events" handled by the program.
They should include the scenarios that show how objects get created and destroyed.
You must then compare the scenarios to check that you are treating the objects in a
consistent manner. At the same time, you make up lists of what objects are asked to do
and what data values you think that they should own.
Products of the first Once you have seen the ways that your putative objects behave in these scenarios
step you compose your initial class descriptions. These will include:
• data owned: e.g. "several histograms, some Lists to store Checkouts, a timer value,
…"
• uses: (summary of requests made to instances of other classes), e.g. "Run() (all
Activity subclasses), Checkout::AddCustomer(), …"
Object based design 1009
The responsibilities of the classes are all the things that you have seen being asked of
prototypical instances in the scenarios that you have composed. It is often worthwhile
noting the classes of client objects that use the functions of a class. In addition, you
should note all the requests made to instances of other classes.
The pattern of requests made to and by an instance of a class identify its "Collaborators"
collaborators. If two objects are to collaborate, they have to have pointers to one
another (e.g. the Shop had a Manager* pointer, and the collaborating Manager had a
Shop* pointer). These pointers must get set before they need to be used.
Setting up the pointers linking collaborators hasn't been a problem in the examples
presented so far. In more complex programs, the establishment of collaboration links
can become an issue. Problems tend to be associated with situations where instances of
one class can be created in different ways (e.g. read from a file or interactive command
from a user). In one situation, it may be obvious that a link should be set to a
collaborator. In the other situation, it may not be so obvious, and the link setting step
may be forgotten. Forgetting links results in problems where a program seems to work
intermittently.
The highlighting of collaborations in the early design stage can act as a reminder so
that later on, when considering how instances of classes are created, you can remember
to check that all required links are being set.
Sometimes you will get a class whose instances get asked to look after data and Isolable components
perform various tasks related to their data, but which don't themselves make requests to
any other objects in the system. They act as "servers" rather than "clients" in all the
"collaborations" in which they participate.
Such classes represent completely isolable components. They should be taken out of
the main development. They can be implemented and tested in isolation. Then they
can be reintroduced as "reusable" classes with the same standing as classes from
standard libraries. The InfoStore example program provides an example; its Vocab
class was isolable in this way.
You will get class hierarchies in two ways. Occasionally, the application problem Class hierarchies
will already have a hierarchy defined. The usual example quoted is a program that must
manipulate "bank accounts". Now "bank accounts" are objects that do various things
like accept debits and credits, report their balance, charge bank fees, and (sometimes)
pay interest. A bank may have several different kinds of account, each with rather
different rules regarding fees and interest payments. Here a hierarchy is obvious from
the start. You have the abstract class "bank_account" which has pure virtual functions
"DeductCharges()" and "AddInterest()". Then there are the various specialized
subclasses ("loan_account", "savings", "checking", "checking_interest") that implement
distinct versions of the virtual functions.
Other cases are more like the Supermarket example. There we had classes Manager,
Door, Checkout, and Customer whose instances all had to behave "in the same way" so
as to make it practical for the simulation system to use a single priority queue. This was
handled by the introduction of an abstraction, class Activity, that became the base
class for the other classes. Class Activity wasn't essential (the Shop could have used
1010 Design and documentation: 2
four different priority queues); but its introduction greatly simplified design. The class
hierarchy is certainly not one that you would have initially expected and doesn't reflect
any "real world" relationship. (How many common features can you identify between
"doors" and "customers"?)
Second step Your initial classes are little more than "fuzzy blob" outlines. You have some idea
as to the data owned and responsibilities but details will not have been defined. For
example, you may have decided that "class Vocab owns the vocabulary and provides
both fast lookup of words and listings of details", or that "class Manager handles the
scheduling rules". You won't necessarily have decided the exact form of the data (hash-
table or tree), you won't have all the data members (generally extra counters and flags
get added to the data members when you get into more detail), and you certainly won't
have much idea as to how the functions work and whether they necessitate auxiliary
functions.
The next step in design is, considering the classes individually, to try to move from a
"fuzzy blob" outline to something with a firm definition. You have to define the types
of all data members (and get into issues like how data members should be initialized).
Each member function has to be considered, possibly being decomposed into simpler
auxiliary private member functions.
Outputs from design The output of this step should be the class declarations and lists of member functions
step like those illustrated in the various examples. Pseudo-code outlines should be provided
for all the more complex member functions.
main() The main() function is usually trivial: create the principle object, tell it to run.
Module structure These programs are generally built from many separate files. The design process
should also cover the module (file) structure and the "header dependencies". Details
should be included with the rest of the design in the form of a diagram like that shown
in 27.6.
Tests As always, some thought has to be given to the testing of individual components and
of the overall program.
Diagrams are a much more important part of the documentation of the design of an
object-based program than they were for the "top-down functional decomposition"
programs.
Your documentation should include:
• "fuzzy" blob diagrams showing the classes and their principle roles (e.g. Figures
22.1 and 22.9);
• a hierarchy diagram (if needed); this could be defined in terms of the fuzzy blob
classes (e.g. Figure 27.4) or the later design classes;
• class "design diagrams" that summarize the data and function members of a class,
(e.g. Figures 27.8 and 27.9);
• module structure;