📄 ch19.htm
字号:
<P>
<LI>The second problem is that these objects call the <TT>PrintString</TT> method
internally. If you did not declare the
method as <TT>virtual</TT>, you would have
to figure out some way for the <TT>ShowHierarchy</TT> method to know whether it should
call the implementation of <TT>PrintString</TT> that is part of <TT>TMyObject</TT>,
or whether it should call the
implementation that is part of <TT>THierarchy</TT>.
By declaring a method as <TT>virtual</TT>, this chore will be handled for you automatically.
If you create an instance of <TT>THierarchy</TT>, <TT>ShowHierarchy</TT> will call
<TT>THierarchy->PrintString</TT> automatically; and if you create an instance
of <TT>TMyObject</TT>, it will call <TT>TMyObject->PrintString</TT>. This is the
way OOP handles virtual methods, and it is one of the key concepts that makes this
system work.
<P>
<LI>The final, and best, reason for doing things this way has to do with polymorphism.
As such, it may not be clear to all readers at this time, but I promise that it will
make sense after you have read the polymorphism chapter.
You can declare a pointer
of type <TT>TMyObject</TT> that can be assigned to a pointer of type <TT>THierarchy</TT>.
When you then call <TT>TMyObject->PrintString()</TT>, the <TT>PrintString</TT>
method of <TT>THierarchy</TT> will be called even
though the object instance is of
type <TT>TMyObject</TT>. This would not occur if <TT>PrintString</TT> were not declared
<TT>virtual</TT>. In fact, this behavior is what polymorphism is all about, and indeed,
it is one of the cornerstones of OOP
programming.
</UL>
<P>The word virtual is inherited from one class to the next. If a base class declares
a method <TT>virtual</TT>, the descendants need not do so, because they will inherit
the <TT>virtual</TT> declaration for a particular method
from the base class. Delphi
users take note, as this is the exact opposite of what happens in Object Pascal.
I should add that it is generally considered bad form not to repeat the declaration
in child objects, as you want to be sure the reader of
your code can see at a glance
how it is structured.</P>
<P>Here is a look at a stripped-down descendant of <TT>TMyObject</TT> that overrides
the <TT>PrintString</TT> method:</P>
<PRE><FONT COLOR="#0066FF">class THierarchy: public TMyObject
{
int
FColor;
public:
THierarchy() : TMyObject() {}
virtual void PrintString(AnsiString S);
__property int Color={read=FColor,write=FColor};
};
</FONT></PRE>
<P>This declaration states that class <TT>THierarchy</TT> is a descendant of class
<TT>TMyObject</TT> and that it overrides <TT>PrintString</TT>.
<DL>
<DT></DT>
</DL>
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE:</B></FONT><B> </B>Delphi programmers beware! The Pascal
object model performs the same chore by using the
<TT>override</TT> directive. That's
not the way C++ works. This is a major change between the new BCB code and the old
Object Pascal techniques.
<HR>
</BLOCKQUOTE>
<P>A first take on the new version of the <TT>PrintString</TT> method looks like
this:</P>
<PRE><FONT COLOR="#0066FF">void THierarchy::PrintString(AnsiString S)
{
char Temp[250];
textcolor(FColor);
sprintf(Temp, "%s\n\r", S.c_str());
cputs(Temp);
}
</FONT></PRE>
<P>This code depends on functionality from
<TT>Conio.h</TT> that allows you to print
strings to the screen in color.</P>
<P>You can see that a field called <TT>FColor</TT> has been added to this object:
<TT>THierarchy</TT> now contains not only procedures but also data. One of the key
aspects
of class declarations is that they can contain both methods and data, so
that you can bring all the code related to the <TT>THierarchy</TT> object together
in one place. This is part of a concept called encapsulation, explained in the next
chapter.
<DL>
<DT></DT>
</DL>
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE:</B></FONT><B> </B>By convention, VCL objects declare
private data as starting with the letter F, which stands for field. This technique
is helpful, because it highlights
the difference between an object's properties and
its data. A property is published to the world and does not begin with the letter
F. Private data is inaccessible to the rest of the world and begins with the letter
F.
<HR>
</BLOCKQUOTE>
<P>When you run the OBJECT2 program, the following code is executed in its main body:</P>
<PRE><FONT COLOR="#0066FF">int main(void)
{
TMyObject *MyObject = new TMyObject();
MyObject->ShowHierarchy();
MyObject->Free();
THierarchy
*Hierarchy = new THierarchy();
Hierarchy->Color = GREEN;
Hierarchy->ShowHierarchy();
Hierarchy->Free();
getch();
return 0;
}
</FONT></PRE>
<P>This code creates an object of type <TT>THierarchy</TT> and then shows you how
to
use the new functionality of the <TT>ShowHierarchy</TT> method. I also create
an instance of <TT>TMyObject</TT> so that you can compare the two classes demonstrated
so far in this chapter.
<DL>
<DT></DT>
</DL>
<BLOCKQUOTE>
<P>
<HR>
<FONT
COLOR="#000077"><B>NOTE:</B></FONT><B> </B>I should perhaps mention that it
is more expensive to declare or call virtual methods than it is to call a static
method. As a result, you need to weigh the whole issue of whether you want to declare
a
method to be <TT>virtual</TT>. <BR>
<BR>
In my opinion, you should usually create objects that have the best possible design,
re-gardless of the amount of overhead they entail. Of course, it is possible to take
this theory too far, but the mere
fact of adding a few <TT>virtual</TT> methods is
usually not the problem in bloated object hierarchies. <BR>
<BR>
Besides space and performance, a second reason for not declaring an object <TT>virtual</TT>
is a desire to hide its implementation so
you can change it later. There is usually
no point in declaring a private method <TT>virtual</TT>, because it can't be seen
by other objects, unless they are friends of the original object. (I will talk about
friend objects later in this chapter.)
The great advantage of private methods is
that they can always be changed later to whatever degree you want, because other
objects usually cannot see them and cannot access them directly. As a result, you
may want to declare methods
<TT>private</TT>, and non-<TT>virtual</TT>, so you can
change their implementation later on. Needless to say, I am referring specifically
to the act of changing the number of parameters these methods take. <BR>
<BR>
Your users will, of course,
complain if you take a method they occasionally want
to override and make it <TT>private</TT> and non-<TT>virtual</TT>. However, it is
sometimes better to listen to their complaints than to saddle them with a broken
object that cannot be fixed
without breaking existing code.
<HR>
</BLOCKQUOTE>
<P>In this section, you have learned about the <TT>virtual</TT> directive. This subject's
true significance won't be clear until you read about polymorphism. However, before
you tackle that
subject, it's best to learn more about inheritance and encapsulation.
In particular, the next section of the chapter looks at more issues involving object
design.
<H4><A NAME="Heading24"></A><FONT COLOR="#000077">Searching for the Right
Design</FONT></H4>
<P>It is almost impossible to find the right design for an object the first time
you write it. As a rule, the only way you can figure out the design for an object
is by creating it, discovering its limitations, and then making
improvements to its
design. In short, object design is an iterative process.</P>
<P>There is no good way to step you through the process of discovering the correct
object design in a book, because the written word is by its nature static, and the
process I'm describing is dynamic. Furthermore, it's confusing to the reader to show
a series of poorly designed objects that are successively improved in each iteration.
The problem with this technique is that the reader keeps seeing the wrong way to
create an object and can easily pick up bad habits or fundamental misconceptions
about object design. Even worse, the reader tends to get frustrated with having to
unlearn the techniques they just acquired in the previous example that are now revealed
as being flawed.</P>
<P>To avoid the problems outlined in the preceding paragraph, I will simply show
you a second version of the <TT>THierarchy</TT> object and explain why it contains
changes to the original version of the object you saw in the last
section. This process
does not tell you much about how I discovered the flaws in the object, but it will
show you what the flaws are and how I got around them. The main point to grasp is
that object design is an iterative process, and that the correct
way to find the
flaws in an object is to implement them once as best you can, and then look for problems.
<DL>
<DT></DT>
</DL>
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE:</B></FONT><B> </B>There is a second school of thought
that
states that you can find the correct design for an object before implementing
it. Proponents of this technique often suggest that a team be split in two, with
part of the members designing objects and the other part implementing them. I have
to
confess that I've never actually discussed the results of this technique with
someone who has used it successfully, as all my experience has been with programmers
who use the iterative technique I discuss here. (Some of these programmers have also
tried the second school of programming, but it did not work for them.) <BR>
<BR>
Of course, you should try to get an object right the first time, and you should use
high-level tools that help with design. However, you should also expect to have to
refine the objects you create through a perhaps lengthy, iterative process. <BR>
<BR>
Furthermore, you should design your object defensively. That is, you should carefully
hide your implementation inside private methods and data because you will
surely
have to change its design at a later time.
<HR>
</BLOCKQUOTE>
<P>The first problem with the original version of the <TT>THierarchy</TT> object
became clear when I wanted to find a method to clear the screen. When doing so, I
needed to
first set the text and background color to which I wanted the screen to
be cleared. As a result, I added new properties and new set methods to the object.
The new property let me add a background color, and the set methods let me change
the text and
background colors at the same time I assigned them to the private data:</P>
<PRE><FONT COLOR="#0066FF">class THierarchy: public TMyObject
{
int FTextColor;
int FBackColor;
protected:
virtual void SetTextColor(int Color)
{ FTextColor =
Color; textcolor(FTextColor); }
virtual void SetBackColor(int Color)
{ FBackColor = Color; textbackground(FBackColor); }
public:
THierarchy() : TMyObject() {}
virtual void PrintString(AnsiString S);
virtual void ClrScr();
__property int TextColor={read=FTextColor,write=SetTextColor};
__property int BackColor={read=FBackColor,write=SetBackColor};
};
</FONT></PRE>
<P>The implementation of <TT>PrintString</TT> now looks like this:</P>
<PRE><FONT COLOR="#0066FF">void
THierarchy::PrintString(AnsiString S)
{
char Temp[250];
sprintf(Temp, "%s\n\r", S.c_str());
cputs(Temp);
}
</FONT></PRE>
<P>Note that I have removed the code that changed the color of the text. This code
is no longer necessary as
the color gets changed in the <TT>SetTextColor</TT> method.</P>
<P>The extremely straightforward implementation of <TT>ClrScr</TT> looks like this:</P>
<PRE><FONT COLOR="#0066FF">void THierarchy::ClrScr()
{
clrscr();
}
</FONT></PRE>
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE:</B></FONT><B> </B>C++ allows you to declare methods
<TT>inline</TT>, as shown by the <TT>SetTextColor</TT> and <TT>SetBackColor</TT>
method declarations. Conversely, you can also declare a method
outside of the object,
as I do in the case of <TT>ClrScr</TT>. This latter technique is called an <TT>out_of_line</TT>
declaration, but that has such a ghastly ring in my ear that I refuse to use it.
You can, in fact, implement a method outside an
object and make it inline by using
the <TT>inline</TT> directive:</P>
<PRE><FONT COLOR="#0066FF">inline void SetTextColor(int Color)
{
FTextColor = Color;
textcolor(FTextColor);
}</FONT></PRE>
<P>Inline methods usually execute faster than
regular methods because they are placed
directly in your code and do not require the overhead associated with a function
call. Whether you implement them inside or outside of a class declaration, you should
declare only very small methods as
<TT>inline</TT>, and they should not contain any
loops. <BR>
<BR>
Inline methods are great, and I use them regularly. The only drawback I see to them
is that they can make object declarations difficult to read. In particular, the great
thing
about an object declaration is that it can provide a summary of the functionality
of an object without asking you to wade through its implementation. Inline methods
implemented inside a class declaration detract from this feature because they
clutter
up the landscape. <BR>
<BR>
One possible workaround is to declare inline functions separately from the object
declaration by using the <TT>inline</TT> keyword. In fact, this is
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -