Delphi Programming Guide
Delphi Programmer 

Menu  Table of contents

Part I - Foundations
  Chapter 1 – Delphi 7 and Its IDE
  Chapter 2 – The Delphi Programming Language
  Chapter 3 – The Run-Time Library
  Chapter 4 – Core Library classes
  Chapter 5 – Visual Controls
  Chapter 6 – Building the User Interface
  Chapter 7 – Working with Forms
Part II - Delphi Object-Oriented Architectures
  Chapter 8 – The Architecture of Delphi Applications
  Chapter 9 – Writing Delphi Components
  Chapter 10 – Libraries and Packages
  Chapter 11 – Modeling and OOP Programming (with ModelMaker)
  Chapter 12 – From COM to COM+
Part III - Delphi Database-Oriented Architectures
  Chapter 13 – Delphi's Database Architecture
  Chapter 14 – Client/Server with dbExpress
  Chapter 15 – Working with ADO
  Chapter 16 – Multitier DataSnap Applications
  Chapter 17 – Writing Database Components
  Chapter 18 – Reporting with Rave
Part IV - Delphi, the Internet, and a .NET Preview
  Chapter 19 – Internet Programming: Sockets and Indy
  Chapter 20 – Web Programming with WebBroker and WebSnap
  Chapter 21 – Web Programming with IntraWeb
  Chapter 22 – Using XML Technologies
  Chapter 23 – Web Services and SOAP
  Chapter 24 – The Microsoft .NET Architecture from the Delphi Perspective
  Chapter 25 – Delphi for .NET Preview: The Language and the RTL
       
  Appendix A – Extra Delphi Tools by the Author
  Appendix B – Extra Delphi Tools from Other Sources
  Appendix C – Free Companion Books on Delphi
       
  Index    
  List of Figures    
  List of tables    
  List of Listings    
  List of Sidebars  

 
Previous Section Next Section

Late Binding and Polymorphism

Pascal functions and procedures are usually based on static or early binding. This means that a method call is resolved by the compiler and linker, which replace the request with a call to the specific memory location where the function or procedure resides (the routine's address). OOP languages allow the use of another form of binding, known as dynamic or late binding. In this case, the actual address of the method to be called is determined at run time based on the type of the instance used to make the call.

This technique is known as polymorphism (a Greek word meaning "many forms"). Polymorphism means you can call a method, applying it to a variable, but which method Delphi actually calls depends on the type of the object the variable relates to. Delphi cannot determine until run time the class of the object the variable refers to, because of the type-compatibility rule discussed in the previous section. The advantage of polymorphism is being able to write simpler code, treating disparate object types as if they were the same and getting the correct runtime behavior.

For example, suppose that a class and an inherited class (let's say TAnimal and TDog) both define a method, and this method has late binding. You can apply this method to a generic variable, such as MyAnimal, which at run time can refer either to an object of class TAnimal or to an object of class TDog. The actual method to call is determined at run time, depending on the class of the current object.

The PolyAnimals example demonstrates this technique. The TAnimal and TDog classes have a Voice method that outputs the sound made by the selected animal, both as text and as sound (using a call to the PlaySound API function defined in the MMSystem unit). The Voice method is defined as virtual in the TAnimal class and is later overridden when you define the TDog class, by the use of the virtual and override keywords:

type
  TAnimal = class
  public
    function Voice: string; virtual;
   
  TDog = class (TAnimal)
  public
    function Voice: string; override;

The effect of the call MyAnimal.Voice depends. If the MyAnimal variable currently refers to an object of the TAnimal class, it will call the method TAnimal.Voice. If it refers to an object of the TDog class, it will call the method TDog.Voice, instead. This happens only because the function is virtual (you can experiment by removing this keyword and recompiling).

The call to MyAnimal.Voice will work for an object that is an instance of any descendant of the TAnimal class, even classes that are defined in other units—or that haven't been written yet! The compiler doesn't need to know about all the descendants in order to make the call compatible with them; only the ancestor class is needed. In other words, this call to MyAnimal.Voice is compatible with all future TAnimal inherited classes.

Note 

This is the key technical reason why object-oriented programming languages favor reusability. You can write code that uses classes within a hierarchy without any knowledge of the specific classes that are part of that hierarchy. In other words, the hierarchy—and the program—is still extensible, even when you've written thousands of lines of code using it. Of course, there is one condition: The ancestor classes of the hierarchy need to be designed very carefully.

In Figure 2.7, you can see an example of the output of the PolyAnimals program. By running it, you'll also hear the corresponding sounds produced by the PlaySound API call.


Figure 2.7: The output of the PolyAnimals example

Overriding and Redefining Methods

As you have just seen, to override a late-bound method in a descendant class, you need to use the override keyword. Note that this can take place only if the method was defined as virtual (or dynamic) in the ancestor class. Otherwise, if it is a static method, there is no way to activate late binding, other than to change the code of the ancestor class.

The rules are simple: A method defined as static remains static in every inherited class, unless you hide it with a new virtual method having the same name. A method defined as virtual remains late-bound in every inherited class (unless you hide it with a static method, which is quite a foolish thing to do). There is no way to change this behavior, because of the way the compiler generates different code for late-bound methods.

To redefine a static method, you add a method to an inherited class having the same parameters or different parameters than the original one, without any further specifications. To override a virtual method, you must specify the same parameters and use the override keyword:

type
  TMyClass = class
    procedure One; virtual;
    procedure Two; {static method}
  end;
   
  TMyDerivedClass = class (MyClass)
    procedure One; override;
    procedure Two;
  end;

Typically, you can override a method two ways: replace the method of the ancestor class with a new version, or add more code to the existing method. This can be accomplished by using the inherited keyword to call the same method of the ancestor class. For example, you can write:

procedure TMyDerivedClass.One;
begin
  // new code
  ...
  // call inherited procedure MyClass.One
  inherited One;
end;

When you override an existing virtual method of a base class, you must use the same parameters. When you introduce a new version of a method in a descendent class, you can declare it with the parameters you want. In fact, this will be a new method unrelated to the ancestor method of the same name—they only happen to use the same name. Here is an example:

type
  TMyClass = class
    procedure One;
  end;
   
  TMyDerivedClass = class (TMyClass)
    procedure One (S: string);
  end;
Note 

Using the class definitions just given, when you create an object of the TMyDerivedClass class, you can call its One method with the string parameter, but not the parameter-less version defined in the base class. If this is what you need, it can be accomplished by marking the redeclared method (the one in the derived class) with the overload keyword. If the method has different parameters than the version in the base class, it becomes effectively an overloaded method; otherwise it replaces the base class method. Notice that the method doesn't need to be marked with overload in the base class. However, if the method in the base class is virtual, the compiler issues the warning "Method 'One' hides virtual method of base type 'TMyClass.'" To avoid this message and to instruct the compiler more precisely on your intentions, you can use the reintroduce directive. If you are interested in this advanced topic, you can find this code in the Reintr example and experiment with it further.

Virtual versus Dynamic Methods

In Delphi, there are two ways to activate late binding. You can declare the method as virtual, as you have seen, or declare it as dynamic. The syntax of the virtual and dynamic keywords is exactly the same, and the result of their use is also the same. What is different is the internal mechanism used by the compiler to implement late binding.

Virtual methods are based on a virtual method table (VMT, also known as a vtable), which is an array of method addresses. For a call to a virtual method, the compiler generates code to jump to an address stored in the nth slot in the object's virtual method table. VMTs allow fast execution of the method calls, but they require an entry for each virtual method for each descendant class, even if the method is not overridden in the inherited class.

Dynamic method calls, on the other hand, are dispatched using a unique number indicating the method, which is stored in a class only if the class defines or overrides it. The search for the corresponding function is generally slower than the one-step table lookup for virtual methods. The advantage is that dynamic method entries only propagate in descendants when the descendants override the method.

Message Handlers

A late-bound method can be used to handle a Windows message, too, although the technique is somewhat different. For this purpose Delphi provides yet another directive, message, to define message-handling methods, which must be procedures with a single var parameter. The message directive is followed by the number of the Windows message the method wants to handle.

Warning 

The message directive is also available in Kylix and is fully supported by the language and the run-time library (RTL). However, the visual portion of the CLX application framework does not use message methods to dispatch notifications to controls. For this reason, whenever possible, you should use a virtual method provided by the library rather than handle a Windows message directly. Of course, this matters only if you want your code to be more portable.

For example, the following code allows you to handle a user-defined message, with the numeric value indicated by the wm_User Windows constant:

type
  TForm1 = class(TForm)
    ...
    procedure WMUser (var Msg: TMessage);
      message wm_User;
  end;

The name of the procedure and the type of the parameters are up to you, although there are several predefined record types for the various Windows messages. You could later generate this message, invoking the corresponding method, by writing:

PostMessage (Form1.Handle, wm_User, 0, 0);

This technique can be extremely useful for veteran Windows programmers, who know all about Windows messages and API functions. You can also dispatch a message immediately by calling the SendMessage API or the VCL Perform method.

Abstract Methods

The abstract keyword is used to declare methods that will be defined only in inherited classes of the current class. The abstract directive fully defines the method; it is not a forward declaration. If you try to provide a definition for the method, the compiler will complain. In Delphi, you can create instances of classes that have abstract methods. However, when you try to do so, Delphi's 32-bit compiler issues the warning message "Constructing instance of <class name> containing abstract methods." If you happen to call an abstract method at run time, Delphi will raise an exception, as demonstrated by the AbstractAnimals example (an extension of the PolyAnimals example), which uses the following class:

type
  TAnimal = class
  public
    function Voice: string; virtual; abstract;
Note 

Most other OOP languages use a stricter approach: you cannot generally create instances of classes containing abstract methods.

You might wonder why you would want to use abstract methods. The reason lies in the use of polymorphism. If class TAnimal has the virtual method Voice, every inherited class can redefine it. If it has the abstract method Voice, every inherited class must redefine it.

In early versions of Delphi, if a method overriding an abstract method called inherited, the result was in an abstract method call. Since Delphi 6, the compiler has been enhanced to notice the presence of the abstract method and skip the inherited call. This means you can safely use inherited in every overridden method, unless you specifically want to disable executing some code of the base class.


 
Previous Section Next Section


 


 

Delphi Sources


Copyright © 2004-2024 "Delphi Sources" by BrokenByte Software. Delphi Programming Guide
ร๐๓๏๏เ ยส๎ํ๒เ๊๒ๅ   Facebook   ั๑๛๋๊เ ํเ Twitter