📄 ch19.htm
字号:
AClass = ClassParent();
while (AClass)
{
AClassName = AnsiString(AClass->ClassName());
PrintString(AClassName);
AClass = AClass->ClassParent();
}
}
</FONT></PRE>
<P>This code first writes
the <TT>ClassName</TT> of the current object, which is
<TT>TMyObject</TT>. Then it gets the <TT>ClassParent</TT>, which is <TT>TObject</TT>,
and writes its name to the screen. The code then tries to get <TT>TObject</TT>'s
parent and fails, because
<TT>TObject</TT> has no parent. At this point, <TT>AClass</TT>
is set to <TT>NULL</TT> and the code exits the <TT>while</TT> loop. The output for
the program is shown in Figure 19.3.<BR>
<BR>
<A NAME="Heading15"></A><A HREF="19ebu03.jpg" tppabs="http://pbs.mcp.com/ebooks/0672310228/art/19/19ebu03.jpg">FIGURE
19.3.</A><FONT COLOR="#000077">
</FONT><I>The output from the OBJECT1 program.</I></P>
<P>In this section, you have learned about the <TT>Create</TT>, <TT>Destroy</TT>,
<TT>Free</TT>, <TT>ClassParent</TT>, and <TT>ClassName</TT> methods of
<TT>TObject</TT>.
The declaration of <TT>TObject</TT> shows that several other methods are available
to BCB programmers. However, I do not discuss these methods in depth because they
are either self-explanatory (<TT>InheritsFrom</TT>) or beyond the
scope of this book.
I should mention, however, that some of these routines are used by the compiler itself
when dispatching routines or performing other complex tasks that usually require
RTTI support. These are advanced programming issues that impact
only a very small
percentage of BCB programmers.
<H3><A NAME="Heading16"></A><FONT COLOR="#000077">Virtual Methods</FONT></H3>
<P>Inheritance, in itself, is an interesting feature, but it would not take on much
significance were it not for the
presence of virtual methods. Virtual methods can
be overridden in a descendant class. As such, they provide the key to polymorphism,
which is a trait of OOP programs that enables you to give the same command to two
different objects but have them
respond in different ways. This chapter introduces
polymorphism, but I will leave the more complex aspects of this subject for Chapter
21, titled, appropriately enough, "Polymorphism." Polymorphism is a relatively
difficult subject to grasp;
therefore, I have stretched out a full explanation of
it over several chapters.</P>
<P>Unlike Object Pascal, BCB has only one type of virtual method. This directive
tells the compiler to store the address of the function in a virtual method table.
<DL>
<DT></DT>
</DL>
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE:</B></FONT><B> </B>Delphi programmers should note that
C++ does not support either the dynamic or message directives. In their place, you
can use the <TT>MESSAGE_MAP</TT>
macro.
<HR>
</BLOCKQUOTE>
<P>The OBJECT2 program (shown in Listing 19.2) has one <TT>virtual</TT> method. The
<TT>virtual</TT> method is overridden in a child object. When you are creating the
OBJECT2 program, you should start with the source code
for the OBJECT1 program. Modify
the code by declaring <TT>PrintString</TT> as <TT>virtual</TT> and by creating a
descendant of <TT>TMyObject</TT> called <TT>THierarchy</TT>. Also, don't forget to
make sure the program is set to work as a console
application. If you don't have
this option checked, you can get an <TT>EInOutError</TT> exception. After changing
the setting, you should also rebuild your project so the new option takes effect.</P>
<BLOCKQUOTE>
<P>
<HR>
<FONT
COLOR="#000077"><B>NOTE:</B></FONT><B> </B>When creating one project based
on another, you can often copy the code from the directory where the first project
is stored into a separate directory made for the second project. After copying the
project, it is probably simplest to delete everything but the actual source files
from the new directory. For instance, delete the DSK file, the MAK file, and any
other extraneous files you will not need. Then create a new project, delete its main
form, and add copies of the source files you want to reuse from the previous project.
Otherwise, you might find paths hard-coded into your DSK or MAK files that address
files stored in the first program's directory. In this particular case, it is
probably
easiest just to re-create the project entirely from scratch, but the information
in this note can be used as a general set of guidelines for use when copying projects
from one directory to another.
<HR>
</BLOCKQUOTE>
<P><A
NAME="Heading19"></A><FONT COLOR="#000077"><B>Listing 19.2. The source code
for the main unit in the OBJECT2 program.</B></FONT></P>
<PRE><FONT COLOR="#0066FF">///////////////////////////////////////
// Object2.cpp
// Project: Object2
// Copyright
(c) 1997 by Charlie Calvert
//
#include <vcl/vcl.h>
#include <stdio.h>
#include <conio.h>
class TMyObject :public TObject
{
public:
TMyObject() : TObject() {}
void ShowHierarchy();
virtual void
PrintString(AnsiString S);
};
class THierarchy: public TMyObject
{
int FColor;
public:
THierarchy() : TMyObject() {}
virtual void PrintString(AnsiString S);
__property int Color={read=FColor,write=FColor};
};
void
TMyObject::PrintString(AnsiString S)
{
printf("%s\n", S.c_str());
}
void TMyObject::ShowHierarchy()
{
TClass AClass;
AnsiString AClassName = AnsiString(ClassName()).c_str();
PrintString(AClassName);
AClass =
ClassParent();
while (AClass)
{
AClassName = AnsiString(AClass->ClassName());
PrintString(AClassName);
AClass = AClass->ClassParent();
}
}
void THierarchy::PrintString(AnsiString S)
{
char Temp[250];
textcolor(FColor);
sprintf(Temp, "%s\n\r", S.c_str());
cputs(Temp);
}
int main(void)
{
TMyObject *MyObject = new TMyObject();
MyObject->ShowHierarchy();
MyObject->Free();
THierarchy *Hierarchy = new
THierarchy();
Hierarchy->Color = YELLOW;
Hierarchy->ShowHierarchy();
Hierarchy->Free();
getch();
return 0;
}
</FONT></PRE>
<P>In OBJECT1, the <TT>ShowHierarchy</TT> method wrote its output to the screen.
Suppose that you
found this object somewhere and liked the way it worked but wanted
to change its behavior so it could also write its output in color. The OBJECT2 program
shows a preliminary version of how you might proceed. After completing this first
take on
creating a descendant of <TT>TMyObject</TT>, I will revisit the subject and
show ways to improve the model shown here. The output from the program is shown in
Figure 19.4.<BR>
<BR>
<A NAME="Heading20"></A><A HREF="19ebu04.jpg" tppabs="http://pbs.mcp.com/ebooks/0672310228/art/19/19ebu04.jpg">FIGURE
19.4.</A><FONT COLOR="#000077">
</FONT><I>The output from the OBJECT2 program.</I></P>
<P>In the old world of structured programming, the most likely step would be to rewrite
the original <TT>ShowHierarchy</TT> method. However, rewriting an existing
method
can be a problem for two reasons:
<UL>
<LI>You might not have the source code to the routine, so you can't rewrite it. This
is a common problem because most programming tools are delivered in binary libraries.
<P>
<LI>You might have the
source code but also know that this particular method is already
being called by several different programmers. You, therefore, might be afraid to
rewrite it because you might break the other programmers' code. Furthermore, making
changes like this
cuts you off from the upgrade path provided by the maker of the
library. You can't just use the maker's next version of the product out of the box,
because you now have a customized version of his or her library.
</UL>
<P>A combination of design
and maintenance issues might deter the impulse to rewrite
the original method. Many projects have been delayed or mothballed because changes
in their designs have broken existing code and thrown the entire project into chaos.</P>
<P>OOP has a simple
solution to this whole problem. Instead of declaring <TT>TMyObject</TT>
as</P>
<PRE><FONT COLOR="#0066FF">class TMyObject :public TObject
{
public:
TMyObject() : TObject() {}
void ShowHierarchy();
void PrintString(AnsiString S);
};
</FONT></PRE>
<P>thoughtful programmers declare it like this:</P>
<PRE><FONT COLOR="#0066FF">class TMyObject :public TObject
{
public:
TMyObject() : TObject() {}
void ShowHierarchy();
virtual void PrintString(AnsiString S);
};
</FONT></PRE>
<P>The difference is that in the second example, the <TT>PrintString</TT> method
is declared as <TT>virtual</TT>.</P>
<P>If <TT>PrintString</TT> is declared as <TT>virtual</TT>, you can override it in
a descendant object, thereby
changing the way the method works without ever changing
the original version of the method. This means that all the other code that relies
on the first version of the program continues to work, and yet you can rewrite the
function for your own
purposes. Furthermore, this technique would work even if you
didn't have the source code for <TT>TMyObject</TT>! I will show you how this works
in just a moment.</P>
<P>Some readers might have asked themselves earlier why I created the
<TT>PrintString</TT>
method in the first place. The answer hinges on the fact that iterating through a
hierarchy of VCL objects can always be accomplished by the same algorithm. The same
technique works for all VCL objects. But the act of printing
information to the screen
changes depending on your current circumstances. Are you in DOS? Are you in Windows?
Do you want to use colors? Each of these circumstances calls for a different way
of printing information to the screen. As a result, I
separated the screen IO portion
of <TT>TMyObject</TT> from the portion of the object that iterates through a hierarchy.
Furthermore, there is no need to declare <TT>ShowHierarchy</TT> as <TT>virtual</TT>,
but I must declare <TT>PrintString</TT> as
<TT>virtual</TT>. The reasoning here is
simply that <TT>ShowHierarchy</TT> does not need to change in descendants of the
object, but <TT>PrintString</TT> will need to change. In particular, it will need
to change so that it can write output in color.
These types of considerations are
part of a subject known as object design.</P>
<P>At this point, you might think that using virtual methods seems like an unnecessarily
opaque solution to this problem. Wouldn't it have been simpler to add new methods
to the inherited class? Then the user of this second class could call these new methods
rather than the ones from the first instance of the class. There are three problems
with this technique:
<UL>
<LI>It requires the user to memorize a whole slew
of different method names that
perform related but slightly different tasks. This is precisely what you have to
do in structured programming, and it is exactly what you want to avoid. Instead,
you want to have one name that applies to all similar
methods of this family. For
instance, if you have an <TT>animal</TT> object, you are going to have to make the
<TT>Walk</TT> method <TT>virtual</TT> because a bird walks on two legs, and a cat
walks on four. The implementation of <TT>Walk</TT> is
different for each animal,
so you need to declare the method <TT>virtual</TT>. When you are done, you can use
<TT>Cat->Walk()</TT>, and the cat will walk properly. Conversely, you can use
<TT>Bird->Walk()</TT>, and the bird will walk
properly. It would be a mess if
you had to use <TT>Bird->WalkOnTwoLegs()</TT> and <TT>Cat->WalkOnFourLegs()</TT>.
This proliferation of similar but slightly different method names is exactly the
kind of structured programming fiasco that OOP
was designed to avoid. Instead of
a bunch of similar names like <TT>WalkOnTwoLegs</TT>, <TT>CrawlOnYourBelly</TT>,
<TT>WalkOnFourLegs</TT>, and <TT>WalkOnOneHundredLegs</TT>, you want to have just
one word, such as <TT>Walk</TT>, that applies to a
whole family of objects. In other
words, each object in the family will implement the <TT>walk</TT> method differently.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -