?? 018.htm
字號(hào):
<TT>InterchangeMessage</TT>, <TT>CISMessage</TT>, <TT>ProdigyMessage</TT>, and so
forth.</P>
<P>Messages are a natural choice for objects in a program handling mail messages,
but finding all the right objects in a complex system is the single greatest challenge
of object-oriented programming. In some cases, such as with messages, the primary
objects seem to "fall out" of your understanding of the problem. More often,
however, you have to think long and hard about what you are trying to accomplish
to find the right objects.</P>
<P>Don't despair. Most designs are not perfect the first time. A good starting point
is to describe the problem out loud. Make a list of all the nouns and verbs you use
when describing the project. The nouns are good candidates for objects. The verbs
might be the methods of those objects (or they may be objects in their own right).
This is not a foolproof method, but it is a good technique to use when getting started
on your design.</P>
<P>That was the easy part. Now the question arises, "Should the message header
be a separate class from the body?" If so, do you need parallel hierarchies,
<TT>CompuServeBody</TT> and <TT>CompuServeHeader</TT>, as well as <TT>ProdigyBody</TT>
and <TT>ProdigyHeader</TT>?</P>
<P>Parallel hierarchies are often a warning sign of a bad design. It is a common
error in object-oriented design to have a set of objects in one hierarchy, and a
matching set of "managers" of those objects in another. The burden of keeping
these hierarchies up-to-date and in sync with each other soon becomes overwhelming:
a classic maintenance nightmare.</P>
<P>There are no hard-and-fast rules, of course, and at times such parallel hierarchies
are the most efficient way to solve a particular problem. Nonetheless, if you see
your design moving in this direction, you should rethink the problem; there may be
a more elegant solution available.</P>
<P>When the messages arrive from the e-mail provider, they will not necessarily be
separated into header and body; many will be one large stream of data, which your
program will have to disentangle. Perhaps your hierarchy should reflect that idea
directly.</P>
<P>Further reflection on the tasks at hand leads you to try to list the properties
of these messages, with an eye towards introducing capabilities and data storage
at the right level of abstraction. Listing properties of your objects is a good way
to find the data members, as well as to "shake out" other objects you might
need.</P>
<P>Mail messages will need to be stored, as will the user's preferences, phone numbers,
and so forth. Storage clearly needs to be high up in the hierarchy. Should the mail
messages necessarily share a base class with the preferences?
<H3 ALIGN="CENTER"><A NAME="Heading18"></A><FONT COLOR="#000077">Rooted Hierarchies
Versus Non-Rooted Hierarchies</FONT></H3>
<P>There are two overall approaches to inheritance hierarchies: you can have all,
or nearly all, of your classes descend from a common root class, or you can have
more than one inheritance hierarchy. An advantage of a common root class is that
you often can avoid multiple inheritance; a disadvantage is that many times implementation
will percolate up into the base class.</P>
<BR>
<DL>
<DD>
<HR>
<FONT COLOR="#000077"><B>New Term: </B></FONT>A set of classes is <I>rooted</I> if
all share a common ancestor. <I>Non-rooted</I> hierarchies do not all share a common
base class.
<HR>
<BR>
</DL>
<BR>
<P>Because you know that your product will be developed on many platforms, and because
multiple inheritance is complex and not necessarily well supported by all compilers
on all platforms, your first decision is to use a rooted hierarchy and single inheritance.
You decide to identify those places where multiple inheritance might be used in the
future, and to design so that breaking apart the hierarchy and adding multiple inheritance
at a later time need not be traumatic to your entire design.</P>
<P>You decide to prefix the name of all of your internal classes with the letter
<TT>p</TT> so that you can easily and quickly tell which classes are yours and which
are from other libraries. On Day 21, "What's Next," you'll learn about
name spaces, which can reinforce this idea, but for now the initial will do nicely.</P>
<P>Your root class will be <TT>pObject</TT>; virtually every class you create will
descend from this object. <TT>pObject</TT> itself will be kept fairly simple; only
that data which absolutely every item shares will appear in this class.</P>
<P>If you want a rooted hierarchy, you'll want to give the root class a fairly generic
name (like <TT>pObject</TT>) and few capabilities. The point of a root object is
to be able to create collections of all its descendants and refer to them as instances
of <TT>pObject</TT>. The trade-off is that rooted hierarchies often percolate interface
up into the root class. You will pay the price; by percolating these interfaces up
into the root object, other descendants will have interfaces that are inappropriate
to their design. The only good solution to this problem, in single inheritance, is
to use templates. Templates are discussed tomorrow.</P>
<P>The next likely candidates for top of the hierarchy status are <TT>pStored</TT>
and <TT>pWired</TT>. <TT>pStored</TT> objects are saved to disk at various times
(for example when the program is not in use), and <TT>pWired</TT> objects are sent
over the modem or network. Because nearly all of your objects will need to be stored
to disk, it makes sense to push this functionality up high in the hierarchy. Because
all the objects that are sent over the modem must be stored, but not all stored objects
must be sent over the wire, it makes sense to derive <TT>pWired</TT> from <TT>pStored</TT>.</P>
<P>Each derived class acquires all the knowledge (data) and functionality (methods)
of its base class, and each should add one discrete additional ability. Thus, <TT>pWired</TT>
may add various methods, but all are in service of adding the ability to be transferred
over the modem.</P>
<P>It is possible that all wired objects are stored, or that all stored objects are
wired, or that neither of these statements is true. If only some wired objects are
stored, and only some stored objects are wired, you will be forced either to use
multiple inheritance or to "hack around" the problem. A potential "hack"
for such a situation would be to inherit, for example, <TT>Wired</TT> from <TT>Stored</TT>,
and then for those objects that are sent via modem, but are never stored, to make
the stored methods do nothing or return an error.</P>
<P>In fact, you realize that some stored objects clearly are not wired: for example,
user preferences. All wired objects, however, are stored, and so your inheritance
hierarchy so far is as reflected in Figure 18.1.<BR>
<BR>
<A NAME="Heading19"></A><A HREF="18zcp01.jpg" tppabs="http://pbs.mcp.com/ebooks/0672310708/art/ch18/18zcp01.jpg"><FONT COLOR="#000077">Figure
18.1.</FONT></A><FONT COLOR="#000077"> </FONT><I>Initial inheritance hierarchy.</I>
<H3 ALIGN="CENTER"><A NAME="Heading20"></A><FONT COLOR="#000077">Designing the Interfaces</FONT></H3>
<P>It is important at this stage of designing your product to avoid being concerned
with implementation. You want to focus all of your energies on designing a clean
interface among the classes and then delineating what data and methods each class
will need.</P>
<P>It is often a good idea to have a solid understanding of the base classes before
trying to design the more derived classes, so you decide to focus on <TT>pObject</TT>,
<TT>pStored</TT>, and <TT>pWired</TT>.</P>
<P>The root class, <TT>pObject</TT>, will only have the data and methods that are
common to everything on your system. Perhaps every object should have a unique identification
number. You could create <TT>pID</TT> (PostMaster ID) and make that a member of <TT>pObject</TT>;
but first you must ask yourself, "Does any object that is not stored and not
wired need such a number?" That begs the question, "Are there any objects
that are not stored, but that are part of this hierarchy?"</P>
<P>If there are no such objects, you may want to consider collapsing <TT>pObject</TT>
and <TT>pStored</TT> into one class; after all, if all objects are stored, what is
the point of the differentiation? Thinking this through, you realize that there may
be some objects, such as address objects, that it would be beneficial to derive from
<TT>pObject</TT>, but that will never be stored on their own; if they are stored,
they will be as part of some other object.</P>
<P>That says that for now having a separate <TT>pObject</TT> class would be useful.
One can imagine that there will be an address book that would be a collection of
<TT>pAddress</TT> objects, and while no <TT>pAddress</TT> will ever be stored on
its own, there would be utility in having each one have its own unique identification
number. You tentatively assign <TT>pID</TT> to <TT>pObject</TT>, and this means that
<TT>pObject</TT>, at a minimum, will look like this:</P>
<PRE><FONT COLOR="#0066FF" font style="font-size:10pt">class pObject
{
public:
pObject();
~pObject();
pID GetID()const;
void SetID();
private:
pID itsID;
}
</FONT></PRE>
<P>There are a number of things to note about this class declaration. First, this
class is not declared to derive from any other; this is your root class. Second,
there is no attempt to show implementation, even for methods such as <TT>GetID()</TT>
that are likely to have inline implementation when you are done.</P>
<P>Third, <TT>const</TT> methods are already identified; this is part of the interface,
not the implementation. Finally, a new data type is implied: <TT>pID</TT>. Defining
<TT>pID</TT> as a type, rather than using, for example, <TT>unsigned long</TT>, puts
greater flexibility into your design.</P>
<P>If it turns out that you don't need an <TT>unsigned long</TT>, or that an <TT>unsigned
long</TT> is not sufficiently large, you can modify <TT>pID</TT>. That modification
will affect every place <TT>pID</TT> is used, and you won't have to track down and
edit every file with a <TT>pID</TT> in it.</P>
<P>For now, you will use <TT>typedef</TT> to declare <TT>pID</TT> to be <TT>ULONG</TT>,
which in turn you will declare to be <TT>unsigned long</TT>. This raises the question:
Where do these declarations go?</P>
<P>When programming a large project, an overall design of the files is needed. A
standard approach, one which you will follow for this project, is that each class
appears in its own header file, and the implementation for the class methods appears
in an associated CPP file. Thus, you will have a file called <TT>OBJECT.HPP</TT>
and another called <TT>OBJECT.CPP</TT>. You anticipate having other files such as
<TT>MSG.HPP</TT> and <TT>MSG.CPP</TT>, with the declaration of <TT>pMessage</TT>
and the implementation of its methods, respectively.
<BR>
<BR>
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE:</B></FONT><B> </B>Buy it or write it? One question
that you will confront throughout the design phase of your program is which routines
might you buy and which must you write yourself. It is entirely possible that you
can take advantage of existing commercial libraries to solve some or all of your
communications issues. Licensing fees and other non-technical concerns must also
be resolved. It is often advantageous to purchase such a library, and to focus your
energies on your specific program, rather than to "reinvent the wheel"
about secondary technical issues. You might even want to consider purchasing libraries
that were not necessarily intended for use with C++, if they can provide fundamental
functionality you'd otherwise have to engineer yourself. This can be instrumental
in helping you hit your deadlines.
<HR>
<BR>
<BR>
</BLOCKQUOTE>
<BR>
<H4 ALIGN="CENTER"><A NAME="Heading21"></A><FONT COLOR="#000077">Building a Prototype</FONT></H4>
<P>For a project as large as PostMaster, it is unlikely that your initial design
will be complete and perfect. It would be easy to become overwhelmed by the sheer
scale of the problem, and trying to create all the classes and to complete their
interface before writing a line of working code is a recipe for disaster.</P>
<P>There are a number of good reasons to try out your design on a prototype--a quick-and-dirty
working example of your core ideas. There are a number of different types of prototypes,
however, each meeting different needs.</P>
<P>An interface design prototype provides the chance to test the look and feel of
your product with potential users.</P>
<P>A functionality prototype might be designed that does not have the final user
interface, but allows users to try out various features, such as forwarding messages
or attaching files without worrying about the final interface.</P>
<P>Finally, an architecture prototype might be designed to give you a chance to develop
a smaller version of the program and to assess how easily your design decisions will
"scale up," as the program is fleshed out.</P>
<P>It is imperative to keep your prototyping goals clear. Are you examining the user
interface, experimenting with functionality, or building a scale model of your final
product? A good architecture prototype makes a poor user interface prototype, and
vice versa.</P>
<P>It is also important to keep an eye on over-engineering the prototype, or becoming
so concerned with the investment you've made in the prototype that you are reluctant
to tear the code down and redesign as you progress.
<H4 ALIGN="CENTER"><A NAME="Heading22"></A><FONT COLOR="#000077">The 80/80 Rule</FONT></H4>
<P>A good design rule of thumb at this stage is to design for those things that 80
percent of the people want to do 80 percent of the time, and to set aside your concerns
about the remaining 20 percent. The "boundary conditions" will need to
be addressed sooner or later, but the core of your design should focus on the 80/80.</P>
<P>In the face of this, you might decide to start by designing the principal classes,
setting aside the need for the secondary classes. Further, when you identify multiple
classes that will have similar designs with only minor refinements, you might choose
to pick one representative class and focus on that, leaving until later the design
and implementation of its close cousins.
<BR>
<BR>
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE:</B></FONT><B> </B>There is another rule, the 80/20
rule, which states that "the first 20% of your program will take 80% of your
time to code, and the remaining 80% of your program will take the other 80% of your
time!"
<HR>
<BR>
<BR>
</BLOCKQUOTE>
<BR>
<H3 ALIGN="CENTER"><A NAME="Heading23"></A><FONT COLOR="#000077">Designing the PostMasterMessage
Class</FONT></H3>
<P>In keeping with these considerations, you decide to focus on <TT>PostMasterMessage</TT>.
This is the class that is most under your direct control.</P>
<P>As part of its interface, <TT>PostMasterMessage</TT> will need to talk with other
types of messages, of course. You hope to be able to work closely with the other
message providers and to get their message format specifications, but for now you
can make some smart guesses just by observing what is sent to your computer as you
use their services.</P>
<P>In any case, you know that every <TT>PostMasterMessage</TT> will have a sender,
a recipient, a date, and a subject, as well as the body of the message and perhaps
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -