|  | Click Here!

Search the site:
EXPERT SEARCH

| Programming Languages
| Databases
| Security
| Web Services
| Network Services
| Middleware
| Components
| Operating Systems
| User Interfaces
| Groupware & Collaboration
| Content Management
| Productivity Applications
| Hardware
| Fun & Games

EarthWeb sites
Crossnodes
Datamation
Developer.com
DICE
EarthWeb.com
EarthWeb Direct
ERP Hub
Gamelan
GoCertify.com
HTMLGoodies
Intranet Journal
IT Knowledge
IT Library
JavaGoodies
JARS
JavaScripts.com
open source IT
RoadCoders
Y2K Info

| ## Charlie Calvert's C++ Builder Unleashed ## - 21 - ## Polymorphism ### Overview In this chapter, you will learn about an esoteric but important subject called polymorphism. If you use object-oriented programming (OOP) but skip polymorphism, you miss out on a key tool that yields robust, flexible architectures. You don't need to understand polymorphism or much about objects to program in BCB. However, if you want to be an expert BCB programmer and want to create components, you should master this material. In this chapter, I take you through several fairly simple programs designed to illustrate key aspects of polymorphism. By the time you're done, you should understand why the simple ability to assign a child object to a parent object is one of the most important features of the entire C++ language. ### Polymorphism from 20,000 Feet Polymorphism can be confusing even to experienced OOP programmers. My explanation of this subject starts with a high-level overview of the theoretical issues that lie at the core of this field of study. The text then moves on to show some real-world examples and finally comes back to a second take on the high-level overview. Don't panic if you don't understand the information in the next few paragraphs right away. I cover this material several times in several different ways, and by the time you finish this chapter, all should be clear to you. Polymorphism is a technique that allows you to set a parent object equal to one or more of its child objects:
    Parent = Child;
The interesting thing about this technique is that, after the assignment, the parent acts in different ways, depending on the traits of the child that is currently assigned to it. One object, the parent, therefore acts in many different ways--hence the name "polymorphism," which translates literally from its Greek roots to an English phrase similar to "many shapes." NOTE: I find the words "assign" and "equal" extremely tricky. If I'm speaking off the cuff and use the word "assign" in three consecutive sentences, I will trip over myself for sure! Because this seemingly simple subject is so tricky, I'm going to take a moment to lay out some rules that you can reference while you're reading this chapter. Consider the following code fragment:

    Parent = Child;

In this simple statement, the child is assigned to its parent. The parent is not assigned to its child. You could easily argue that this definition could be reversed, but I do not take that approach in this chapter.
When referencing the preceding code fragment, I also say that the parent is set equal to its child. The child is not set equal to its parent. Once again, you can argue that this definition could be reversed, or that the action is reflexive. Throughout this chapter, however, I consistently state that the preceding code sets a parent equal to its child, and not a child equal to its parent.
Because these seemingly simple English phrases are so confusing, at least to me, I try to illustrate exactly what I mean as often as possible. That is, I say the statement in English, insert a colon, and then follow the English phrase with code that provides an example of the meaning of my statement. I find the English phrases ambiguous and confusing, but the code is starkly clear. Don't waste too much time parsing the English; concentrate on the code!

The classic example of polymorphism is a series of objects, all of which do the following:

- Descend from one base class

- Respond to a virtual command called `Draw`

- Produce different outcomes

For instance, you might have four objects called `TRectangle`, `TEllipse`, `TCircle`, and `TSquare`. Suppose that each of these objects is a descendant of a base class called `TShape`, and that `TShape` has a virtual method called `Draw`. (This hypothetical `TShape` object is not necessarily the one that appears on BCB's component palette.) All the children of `TShape` also have `Draw` methods, but one draws a circle; one, a square; the next, a rectangle; and the last, an ellipse. You could then assign any of these objects to a variable of type `TShape`, and that `TShape` variable would act differently after each assignment. That is, the object of type `TShape` would draw a square if set equal to a `TSquare` object:

    TShape *Shape = Square;

    Shape->Draw();   // Draws a square.

It would draw an ellipse if set equal to a `TEllipse` object:

    TShape *Shape = Ellipse;

    Shape->Draw();  // Draws an ellipse;

and so on.

Notice that in both these cases the object that does the drawing is of type `TShape`. In both cases, the same command of `TShape` is called. You would therefore expect that a call to `Shape->Draw()` would always produce the same results. Polymorphism puts the lie to this logic. It allows a method of an object to act in many different ways. One object, called `Shape`, "morphs" from one set of functionality to another, depending on the context of the call. That's polymorphism.

From a conceptual point of view, this description does much to explain what polymorphism is all about. However, I still need to explain one key aspect.

According to the rules of OOP, you can pass all these objects to a single function that takes an object of type `TShape` as a parameter. That single function can call the `Draw` method of each of these objects, and each one will behave differently:

    void DrawIt(TShape *Shape)

    {

      Shape->Draw(); // TShape draws different shapes depending on "assignment"

    }

    void DoSomething()

    {

      TRectangle *Rectangle = new TRectangle();

      TSquare *Square = new TSquare();

      TEllipse *Ellipse = new TEllipse();

      DrawIt(Rectangle);           // Draws a rectangle

      DrawIt(Square);              // Draws a square

      DrawIt(Ellipse);             // Draws an ellipse

      delete Rectangle;

      delete Square;

      delete Ellipse;

    }

When you pass an object of type `TRectangle` to a function that takes a `TShape` as a parameter, you are accessing the `TRectangle` object through an object of type `TShape`. Or, if you look at the act of passing a parameter from a slightly different angle, you're actually assigning a variable of type `TRectangle` to a variable of type `TShape`:

    Shape = Rectangle;

This assignment is the actual hub around which polymorphism revolves. Because this assignment is legal, you can use an object of a single type yet have it behave in many different ways: Once again, that's polymorphism.

> NOTE: Grasping the idea behind polymorphism is a bit like grasping the idea behind pointers. Many programmers have a hard time understanding pointers when they first see them. Then, after a time, manipulating pointers becomes as natural as tying their shoes. It no longer requires thought. The same is true of polymorphism. The concept can seem quite opaque at first to many programmers, and then they have a little epiphany. Then wham! Suddenly their coding ability makes the same kind of huge jump forward that occurred when they learned about pointers.
> Polymorphism has the same relationship to OOP that pointers have to C or C++. People might say they are C++ programmers, but if they don't understand pointers, they are missing out on at least half of what the language has to offer. The same is true of OOP and polymorphism. Many programmers claim to understand OOP, but if they don't yet see what polymorphism is all about, they are missing at least half the power of technology.

To fully understand the preceding few paragraphs, you have to grasp that children of an object are assignment-compatible with their parents. Consider the following declaration:

    class TParent

    {

    }

    class TChild: public TParent

    {

    }

    Given these declarations, the following is legal:

    {

      TParent *Parent;

      TChild *Child;

      Parent = Child;

    }

But this syntax is flagged as a type mismatch; that is, the compiler will complain that it cannot convert a variable of type `TParent*` to `TChild*`:

    {

      TParent *Parent;

      TChild *Child;

      Child = Parent;

    }

You can't set a child equal to a parent because the child is larger than its parent--that is, it has more methods or fields--and therefore all its fields and methods will not be filled out by the assignment. All other things being equal, you can build a two-story building out of the pieces meant for a three-story building; but you can't build a three-story building out of the pieces meant for a two-story building.

Consider the following hierarchy:

    class TParent: public TObject

    {

      virtual void Draw();

    };

    class TChild: public TParent

    {

      virtual void Draw();

      virtual void ShowHierarchy();

    };

The issue here is that setting a child equal to a parent is not safe:

    Child = Parent; // Don't do this!

If it were allowed, writing the following would be a disaster:

    Child->ShowHierarchy();

In this hypothetical world, the call might compile, but it would fail at runtime because `Parent` has no `ShowHierarchy` method; therefore, it could not provide a valid address for the function at the time of the assignment operation. I will return to this subject in the next section of this chapter.

If you set a parent equal to a child, all the features of the parent will be filled out properly:

    Parent = Child;

That is, all the functions of `TParent` are part of `TChild`, so you can assign one to the other without fear of something going wrong. The methods that are not part of `TParent` are ignored.

> NOTE: When you're thinking about this material, you need to be sure you are reading statements about assigning parents to children correctly. Even if I manage to straighten out my grammar, nothing in the English language makes totally clear which item in an assignment statement is on the left and which is on the right. I could use the terms `lvalue` and `rvalue` in this case, except that they don't quite fit. However, if you take this description as an analogy, you can consider a child to be an `rvalue` and a parent to be an `lvalue`. You can set a parent equal to a child:

>     Parent = Child;

> but you can't set a child equal to a parent:

>     Child  = Parent;

> You literally can't do this. You get a type mismatch. The compiler replies "Cannot convert `TParent*` to `TChild`." In this sense, `Child` becomes like an `rvalue` in this one case. Even though assigning values to `Child` is ultimately possible, you can't assign a `Parent` to it. In this one case, it might as well be an `rvalue`.
> To see this process in action, start a new project in the IDE, drop a button on the form, and write the following code:

>     void __fastcall TForm1::Button1Click(TObject *Sender)

>     {

>       TForm *Form;

>       TComponent *Component;

>       Component = Form;

>       Form = Component;

>     }

> The compiler will allow the first assignment but object to the second. This objection occurs because `TForm` is a descendant of `TComponent`.

### Another View of Polymorphism

Here's another way of looking at polymorphism. A base class defines a certain number of functions that are inherited by all its descendants. If you assign a variable of the child type to one of its parents, all the parent's methods are guaranteed to be filled out with valid addresses. The issue here is that the child, by the very fact of its being a descendant object, must have valid addresses for all the methods used in its parent's virtual method table (VMT). As a result, you can call one of these methods and watch as the child's functions get called. However, you cannot call one of the child's methods that does not also belong to the parent. The parent doesn't know about those methods, so the compiler won't let you call them. In other words, the parent may be able to call some of the child's functions, but it is still a variable of the parent type.

> NOTE: A virtual method table, or VMT, is a table maintained in memory by the compiler; it contains a list of all the pointers to the virtual methods hosted by an object. If you have an object that is descended from `TObject`, the VMT for that object will contain all the virtual methods of that object, plus the virtual methods of `TObject`.

To help you understand this arrangement, picture the VMT for `TParent`. It has a pointer to a `Draw` method in it, but no pointer to a `ShowHierarchy` method. Therefore, an attempt to call its `ShowHierarchy` method would fail, as would an attempt to fill out a `TChild`'s `ShowHierarchy` through an assignment with a `TParent` object.

Consider this schema:

    Simplified VMT for Parent         Simplified VMT for Child

    StartTable                        StartTable

      Draw ------------------------------ Draw

    EndTable                              ShowHierarchy

                                      EndTable

This schema shows a parent being set equal to a child. As you can see, the address of the `Draw` method for the parent is assigned to the address for the `Draw` method for the child. No `ShowHierarchy` method exists in the parent, so it is ignored.

Here's what happens if you try to set the child equal to the parent:

    Simplified VMT for Child          Simplified VMT for Parent

    StartTable                        StartTable

      Draw ------------------------------ Draw

      ShowHierarchy -------------------- ????

    EndTable                          EndTable

As you can clearly see, no method pointer in the parent table can be assigned to the `ShowHierarchy` method of the child table. Therefore, it is left blank, which means a call to the `ShowHierarchy` method of the child almost certainly fails.

Because the `ShowHierarchy` method cannot be filled out properly, assigning `TParent` to `TChild` is illegal. In other words, it's not just a legal technicality, it's a literal impossibility. You literally can't successfully assign a parent to a child. You can, however, assign a child to a parent, and it's this assignment that lies at the heart of polymorphism. For the sake of clarity, let me spell it out. Here is the legal assignment:

    Parent = Child;

Here is the illegal assignment:

    Child = Parent;

Needless to say, I wouldn't be placing so much emphasis on this subject if it were not vitally important. You simply won't really understand OOP unless you grasp what has been said in the last few pages.

### Virtual Methods and Polymorphism

If some of the methods in a base class are defined as virtual, each of the descendants can redefine the implementation of these methods. The key elements that define a typical case of polymorphism are a base class and the descendants that inherit a base class's methods. In particular, the fanciest type of polymorphism involves virtual methods that are inherited from a base class.

A classic example of polymorphism is evident if you examine the BCB VCL. All these objects are descendants of a single base class called `TObject`; therefore, they all inherit a virtual destructor. As a result, you can pass all the many hundreds of BCB classes to a routine that takes a parameter of the same type as their base class:

    void FreeAllClasses(TObject O)

    {

      delete O;

    }

You can pass any VCL object to the `FreeAllClasses` function, and the object will be properly destroyed. The VirtualMethodTest program, shown in Listings 21.1 and 21.2, shows how this process works. You can also find this program on the CD that comes with this book.
Listing 21.1. The header for the VirtualMethodTest program.

    ///////////////////////////////////////

    // Main.h

    // VirtualMethodTest

    // Copyright (c) 1997 by Charlie Calvert

    //

    #ifndef MainH

    #define MainH

    #include <vcl\Classes.hpp>

    #include <vcl\Controls.hpp>

    #include <vcl\StdCtrls.hpp>

    #include <vcl\Forms.hpp>

    class TParent

    {

    public:

      TParent() {}

      virtual __fastcall ~TParent()

        { ShowMessage("TParent Destructor"); }

      virtual void Draw() {}

    };

    class TChild: public TParent

    {

    public:

      TChild(): TParent() {}

      virtual __fastcall ~TChild()

        { ShowMessage("TChild Destructor"); }

      virtual void Draw() {}

      virtual void ShowHierarchy() {}

    };

    class TForm1 : public TForm

    {

    __published:

      TButton *RunTestBtn;

      void __fastcall RunTestBtnClick(TObject *Sender);

    private:

      void FreeObjects(TParent *Parent);

    public:

      virtual __fastcall TForm1(TComponent* Owner);

    };

    extern TForm1 *Form1;

    #endif

Listing 21.2. The main body of the main form for the VirtualMethodTest program.

    ///////////////////////////////////////

    // Main.cpp

    // VirtualMethodTest

    // Copyright (c) 1997 by Charlie Calvert

    //

    #include <vcl\vcl.h>

    #pragma hdrstop

    #include "Main.h"

    #pragma resource "*.dfm"

    TForm1 *Form1;

    __fastcall TForm1::TForm1(TComponent* Owner)

    : TForm(Owner)

    {

    }

    void TForm1::FreeObjects(TParent *Parent)

    {

      delete Parent;

    }

    void __fastcall TForm1::RunTestBtnClick(TObject *Sender)

    {

      TParent *Parent = new TParent();

      TChild *Child = new TChild();

      FreeObjects(Parent);

      FreeObjects(Child);

    }

From a visual point of view, this program is very unexciting, as you can see from a glance at Figure 21.1.
FIGURE 21.1. The main form of the VirtualMethodTest program.

To put this program to work, you need to change the destructors for the `TParent` and `TChild` objects so that they are sometimes declared as virtual and sometimes declared as standard, non-virtual methods:

    class TParent

    {

    public:

      TParent() {}

      virtual __fastcall ~TParent() { ShowMessage("TParent Destructor"); }

      virtual void Draw() {}

    };

    class TParent

    {

    public:

      TParent() {}

      __fastcall ~TParent() { ShowMessage("TParent Destructor"); }

      virtual void Draw() {}

    };

In the first case, the destructor is declared as virtual, and in the second, it is not declared as virtual. Notice that I do not make `TParent` descend from `TObject`, because all VCL classes must have virtual destructors since `TObject` itself declares its destructor as virtual.

When you do not declare the destructors as virtual, and you pass the objects to the `FreeObjects` method, the destructor for `TParent` is called:

    void TForm1::FreeObjects(TParent *Parent)

    {

      delete Parent;

    }

If you declare both destructors as virtual, when you pass them to `FreeObjects`, the destructor for `TChild` will be called, even though it is being called off an instance of `TParent`.

If what I'm saying is not clear, start C++Builder and load the `VirtualMethodTest` program so that you can watch this process in action. Run the program with the destructor declared as virtual and then run it again without the virtual destructor declaration. This material is extremely important. You simply cannot understand what OOP is all about if you do not understand polymorphism.

Remember that passing a parameter to `FreeAllClasses` is analogous to an assignment statement. In effect, you are writing

    Parent = Parent;

    Parent = Child;

You could not, however, write this:

    void TForm1::FreeObjects(TChild *Child)

    {

      delete Child;

    }

    void __fastcall TForm1::Button1Click(TObject *Sender)

    {

      TParent *Parent = new TParent();

      TChild *Child = new TChild();

      FreeObjects(Parent);

      FreeObjects(Child);

    }

The issue, of course, is that by passing `Parent` to `FreeAllClasses`, you are, in effect, asking BCB to make the following assignment:

    Child = Parent;

BCB is kind enough to tell you that making this assignment is bad practice.

Every object in the VCL can use polymorphism to some degree because they all inherit methods from `TObject`. In particular, they all inherit a virtual destructor. Without this virtual destructor, the whole system would collapse. In particular, the component array hosted by each form is iterated at closing time, and the destructor of each object is called. Doing so would not be possible if the destructors of these objects were not declared virtual. Or, rather, doing so would be possible, but the objects themselves would not be properly destroyed. The reason for the failure is illustrated clearly in the VirtualMethodTest program.

To help illustrate this point, I will implement the classic shape demo example described earlier in this chapter. This program creates a parent object called `TMyShape` and two child objects called `TRectangle` and `TCircle`. All three of these objects have virtual methods called `DrawShape`. You can pass a single instance of this object to a method declared as follows:

    void TForm1::CallDrawShape(TMyShape *Shape)

    {

      Shape->DrawShape();

    }

If you pass in an instance of `TMyShape`, then the `TMyShape::DrawShape` method will be called, which is what you would expect. However, if you pass in either a `TRectangle` or `TCircle`, then the respective `DrawShape` methods of each object will be called, as shown in Figure 21.2.
FIGURE 21.2. The ClassicShapeDemo shows how one object can behave in three different ways: on the left it draws a star, in the center a circle, and on the right a square.

The `TForm1::CallDrawShape` method works with a variable of type `TMyShape`. It has only one kind of object in it. It works only with variables of type `TMyShape`. And yet if you pass both a `TRectangle` object and a `TMyShape` object into it, one instance will call the `DrawShape` method of `TRectangle` and the other will call the `DrawShape` method of `TMyShape`. This example illustrates polymorphism in action. The concept here is so important that I have placed this example on disk in a directory called `ClassicShapeDemo` and show it in Listings 21.3 and 21.4.
Listing 21.3. The header for ClassicShapeDemo shows how polymorphism looks in action.

    ///////////////////////////////////////

    // Main.h

    // Classic Shape Demo

    // Copyright (c) 1997 by Charlie Calvert

    //

    #ifndef MainH

    #define MainH

    #include <vcl\Classes.hpp>

    #include <vcl\Controls.hpp>

    #include <vcl\StdCtrls.hpp>

    #include <vcl\Forms.hpp>

    #include <vcl\ExtCtrls.hpp>

    class TMyShape: public TCustomControl

    {

    public:

      virtual __fastcall TMyShape(TComponent *AOwner): TCustomControl(AOwner) {}

      virtual __fastcall TMyShape(TComponent *AOwner, int X, int Y)

        : TCustomControl(AOwner) { Width = 50; Height = 50;  Left = X; Top = Y; }

      virtual void DrawShape()

      { Canvas->Brush->Style = bsClear; Canvas->TextOut(0, 0, "*"); }

    };

    class TCircle: public TMyShape

    {

    public:

      virtual __fastcall TCircle(TComponent *AOwner): TMyShape(AOwner) {}

      virtual __fastcall TCircle(TComponent *AOwner, int X, int Y)

        : TMyShape(AOwner) { Width = 50; Height = 50;  Left = X; Top = Y; }

      virtual void DrawShape()

      { Canvas->Brush->Color = clBlue; Canvas->Ellipse(0, 0, Width, Height); }

    };

    class TRectangle: public TMyShape

    {

    public:

      virtual __fastcall TRectangle(TComponent *AOwner): TMyShape(AOwner) {}

      virtual __fastcall TRectangle(TComponent *AOwner, int X, int Y)

        : TMyShape(AOwner) { Width = 50; Height = 50;  Left = X; Top = Y; }

      virtual void DrawShape()

      { Canvas->Brush->Color = clLime; Canvas->Rectangle(0, 0, Width, Height); }

    };

    class TForm1 : public TForm

    {

    __published:

      TButton *DrawShapesBtn;

      TPanel *Panel1;

      void __fastcall DrawShapesBtnClick(TObject *Sender);

    private:

      void TForm1::DrawShape(TMyShape *Shape);

    public:

      virtual __fastcall TForm1(TComponent* Owner);

    };

    extern TForm1 *Form1;

    #endif

###

Listing 21.4. The main form for the ClassicShapeDemo program.

    ///////////////////////////////////////

    // Main.cpp

    // Classic Shape Demo

    // Copyright (c) 1997 by Charlie Calvert

    //

    #include <vcl\vcl.h>

    #pragma hdrstop

    #include "Main.h"

    #pragma resource "*.dfm"

    TForm1 *Form1;

    __fastcall TForm1::TForm1(TComponent* Owner)

      : TForm(Owner)

    {

    }

    void TForm1::DrawShape(TMyShape *Shape)

    {

      Shape->DrawShape();

    }

    void __fastcall TForm1::DrawShapesBtnClick(TObject *Sender)

    {

      TMyShape *Shape = new TMyShape(Panel1, 10, 10);

      Shape->Parent = Panel1;

      TCircle *Circle = new TCircle(Panel1, 50, 10);

      Circle->Parent = Panel1;

      TRectangle *Rectangle = new TRectangle(Panel1, 140, 10);

      Rectangle->Parent = Panel1;

      DrawShape(Shape);

      DrawShape(Circle);

      DrawShape(Rectangle);

    }

Most of the complexity of this example is a result of the default behavior of VCL objects. Notice that `TMyShape` descends from `TCustomControl`. I chose this lineage because `TCustomControl` has a `Canvas` object ready for use by its consumers. I pay a price for this functionality, because I must create a default constructor with the following signature if I want to suppress valid compiler warnings:

    virtual __fastcall TMyShape(TComponent *AOwner): TCustomControl(AOwner) {}

After I have declared this constructor, I can create a second constructor that suits my own needs:

    virtual __fastcall TMyShape(TComponent *AOwner, int X, int Y)

    : TCustomControl(AOwner) { Width = 50; Height = 50;  Left = X; Top = Y; }

Notice that this constructor gives the object a default height and width, and also assigns values to its `Left` and `Top` fields. All these fields are inherited from `TCustomControl` and must be filled out properly if you want to draw the object on the screen.

> NOTE: `TCustomControl` is one of the objects from which you can make descendants if you want to create a component. In fact, both of the examples shown here are valid components that could be placed on the Component Palette with only a little more work. However, I will save a complete explanation of `TCustomControl` for the next chapter of this book. For now, all you need to know is that these fairly sophisticated objects come replete with `Left`, `Top`, `Width`, and `Height` properties, as well as an easy-to-use `Canvas` object.

When you create an instance of a VCL control, you need to give it both an owner and a parent:

    TCircle *Circle = new TCircle(Panel1, 50, 10);

    Circle->Parent = Panel1;

This code assigns `Panel1` as both the parent and owner of `TCircle`. `Panel1` is just a standard panel to serve as the drawing surface on which the program paints its output.

After you declare constructor objects of type `TMyShape`, `TRectangle`, and `TCircle`, the final step is to pass each of these objects to the `CallDrawShape` method:

    CallDrawShape(Shape);

    CallDrawShape(Circle);

    CallDrawShape(Rectangle);

When you look at it from this end, the fact that `CallDrawShape` would produce different results depending on the type of object you pass into it makes sense. However, if you look at the `CallDrawShape` method itself, the fact that you can pass objects of different types to it is not at all obvious:

    void TForm1::CallDrawShape(TMyShape *Shape)

    {

      Shape->DrawShape();

    }

The odd thing about polymorphism is that you can make the assignment of an object of type `TRectangle` to an object of type `TMyShape`. Once you understand that you can do that, everything else falls out fairly naturally.

If you have any questions about what's going on in this code, run this program. Step through it with the debugger. Do whatever is necessary to make sense of this very important code. The key point to grasp is that one variable, called `Shape`, which is of type `TMyShape`, behaves in three different ways. It's polymorphic--one variable acting in different ways.

### Polymorphism in the VCL

One place where BCB uses polymorphism heavily is with the `TField` objects assigned to the `Fields` array in a `TTable` object. The objects in the `Fields` array are of type `TField`, but they behave as if they are of type `TStringField`, `TIntegerField`, `TFloatField`, and so on. The issue, of course, is that variables of type `TStringField` and `TIntegerField` can all be assigned to the `Fields` array because you can assign a child object to a variable of its parent type:

    Parent = Child;

Here is the declaration of the `Fields` property from the `TDataSet` declaration in `DB.hpp`:

    __property TField* Fields[int Index] = {read=GetField, write=SetField};

Clearly, this array of objects is of type `TField`. Suppose, however, that you execute the following method in a simple program containing a `TTable` object that's pointed to the `Biolife` table:

    void __fastcall TForm1::bViewFieldTypesClick(TObject *Sender)

    {

      int i;

      for (i = 0; i < Table1->FieldCount; i++)

      {

        ListBox1->Items->Add(Table1->Fields[i]->ClassName());

      }

    }

According to the declaration of the `Fields` object, the type of each of the members of the `Fields` array should be `TField`. However, the following is what actually gets printed in the program's list box, as you can see in Figure 21.3.

    TFloatField

    TStringField

    TStringField

    TStringField

    TFloatField

    TFloatField

    TMemoField

    TGraphicField

Clearly, this isn't really an array of `TField` objects at all. So what's going on anyway?
FIGURE 21.3. The PolyFields program shows how polymorphism underlies key parts of the VCL.

Well, by now the answer should be clear. Somewhere internally, BCB is assigning `TStringField`, `TFloatField`, `TMemoField`, and `TGraphicField` objects to the members of the `TField` array. Why is this legal? Because setting a parent object equal to a child object is always legal! In essence, the following happens:

    {

      TField *Field;

      TStringField *StringField;

      Field = StringField;  // This is legal!

    }

Here is the hierarchy of some of the key `TField` descendants:

    -TField

      -TStringField

      -TNumericField

        -TSmallIntField

        -TWordField

        -TAutoIncField

      -TFloatField

        -TCurrencyField

      -TBlobField

        -TMemoField

        -TGraphicField

Given this hierarchy, assigning a `TStringField` or `TFloatField` object to a variable of type `TField` is always legal:

    {

      TField *Field[3];

      TStringField *StringField;

      TFloatField *FloatField;

      TGraphicField *GraphicField;

      Fields[0] = StringField;   // legal!

      Fields[1] = FloatField;    // legal!

      Fields[2] = GraphicField;  // legal!

    };

This point is so important that I'm going to include the source code for a program called `PolyFlds.mak` that demonstrates the issue discussed in this section. Listings 21.5 and 21.6 show this source code.
Listing 21.5. The PolyFields program shows how BCB makes practical use of polymorphism.

    #ifndef MainH

    #define MainH

    #include <vcl\Classes.hpp>

    #include <vcl\Controls.hpp>

    #include <vcl\StdCtrls.hpp>

    #include <vcl\Forms.hpp>

    #include <vcl\DBGrids.hpp>

    #include "Grids.hpp"

    #include <vcl\DBTables.hpp>

    #include <vcl\DB.hpp>

    class TForm1 : public TForm

    {

    __published:

      TDBGrid *DBGrid1;

      TButton *bViewFieldTypes;

      TListBox *ListBox1;

      TTable *Table1;

      TDataSource *DataSource1;

      void __fastcall bViewFieldTypesClick(TObject *Sender);

    private:

    public:

      virtual __fastcall TForm1(TComponent* Owner);

    };

    extern TForm1 *Form1;

    #endif

Listing 21.6. The main form for the PolyFields program.

    #include <vcl\vcl.h>

    #pragma hdrstop

    #include "Main.h"

    #pragma link "Grids"

    #pragma resource "*.dfm"

    TForm1 *Form1;

    __fastcall TForm1::TForm1(TComponent* Owner)

      : TForm(Owner)

    {

    }

    void __fastcall TForm1::bViewFieldTypesClick(TObject *Sender)

    {

      int i;

      for (i = 0; i < Table1->FieldCount; i++)

      {

        ListBox1->Items->Add(Table1->Fields[i]->ClassName());

      }

    }

This sample program makes clear how important polymorphism is to BCB. The `Fields` array of `TTable` is one of the key elements in all of BCB. And what lies at the heart of the whole thing? Polymorphism!

Another important point to grasp here is that very little about BCB is mysterious. If you work with Visual Basic or PowerBuilder, the only explanation for much of the syntax is simply "because." Why does it work that way? Because! With BCB, the explanation is never like that. You can find an explanation for everything in BCB, and you can build any part of the VCL or the environment yourself by using BCB.

Remember: BCB is built into the VCL. You'll find no mysteries here. If you know the language well enough, even the most esoteric parts of the VCL will make sense to you. How can the `Fields` array be so powerful? How does it do those things? Well, now you know the answer: It's polymorphic!

### Polymorphism Encapsulated (Review of Main Points)

I have, at long last, said all I want to say about polymorphism. The key points to remember are these:

- You can set a parent equal to a child object, but you can't set a child equal to a parent object:

>     Parent = Child;  //  Little set equal to big: OK

>     Child = Parent;  //  Big set equal to little: Doesn't work

  Setting a parent equal to a child object is what makes polymorphism tick.

- The defining elements in polymorphism are the methods of the parent object, particularly those methods that are declared virtual. Even if you assign a child to a parent object

>     Parent = Child;

  the parent can't call methods of the child that are not also visible in the parent's class declaration. For example, if you assign a `TForm` instance to a `TObject` instance, you can't access the form's `Caption` property from the `TObject` instance without a typecast.

The point is that you can take a whole slew of hierarchically arranged objects, assign each of them to their parent, call a virtual method belonging to the parent, and watch them all behave in different ways. Polymorphism!

For some readers, I'm sure this information is old hat. Other readers might be new to the subject but have grasped it completely from the descriptions given already. However, most readers probably still have some questions lingering in the backs of their minds.

If you want, just reread the preceding sections as often as necessary. I know from personal experience that as many as three out of four object-oriented programmers don't really understand polymorphism. The subject, however, is not that complicated. Concentrate on these two sentences:

Polymorphism allows you to set one variable of type TParent equal to a series of child objects. When you call certain virtual methods of that parent, it will behave in different ways, depending on the traits of the currently selected child.

That's a mouthful, but the concepts set forth are not impossible to comprehend.

So far, all the examples of polymorphism are very stripped-down programs that contain only code directly relevant to the subject at hand. However, the real power of polymorphism won't become clear until you work with a larger program that really taps into the power of OOP. Unfortunately, the example I have for this kind of programming really cries out to be implemented as a set of polymorphic components. Because I have not yet described how to create components, I will have to delay showing you this program until Chapter 23, "Creating Components from Scratch." In that chapter, you will find a sample program called WareHouse that clearly illustrates what polymorphism looks like in a large, and fairly practical, program.

### Summary

In this chapter, you have tackled the complex subject of polymorphism. You saw that polymorphism is built on the fact that OOP languages enable you to assign a variable of type child to a variable declared to be of type parent. When you then call a method of the parent type, it will go to the address of the child object's methods. As a result, an object of type `TParent`, when assigned to four different descendant objects, might react in four different ways. One object, many different faces: polymorphism.

©Copyright, Macmillan Computer Publishing. All rights reserved.

| Use of this site is subject certain Terms & Conditions. Copyright (c) 1996-1999 EarthWeb, Inc.. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Please read our privacy policy for details.