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

Inheriting from Existing Types

You'll often need to use a slightly different version of an existing class. For example, you might need to add a new method or slightly change an existing one. If you copy and paste the original class and then modify it (certainly a terrible programming practice, unless there is a specific reason to do so), you'll duplicate your code, bugs, and headaches. Instead, in such a circumstance you should use a key feature of OOP: inheritance.

To inherit from an existing class in Delphi, you only need to indicate that class at the beginning of the declaration of the new class. For example, this is done each time you create a new form:

type
  TForm1 = class(TForm)
  end;

This definition indicates that the TForm1 class inherits all the methods, fields, properties, and events of the TForm class. You can call any public method of the TForm class for an object of the TForm1 type. TForm, in turn, inherits some of its methods from another class, and so on, up to the TObject base class.

As an example of inheritance, you can derive a new class from TDate and modify its GetText function. You can find this code in the Dates unit of the NewDate example:

type
  TNewDate = class (TDate)
  public
    function GetText: string;
  end;

To implement the new version of the GetText function, I used the FormatDateTime function, which uses (among other features) the predefined month names available in Windows; these names depend on the user's regional and language settings. (Many of these regional settings are copied by Delphi into constants defined in the library, such as LongMonthNames, ShortMonthNames, and others you can find under the "Currency and date/time formatting variables" topic in the Delphi Help file.) Here is the GetText method, where 'dddddd' stands for the long date format:

function TNewDate.GetText: string;
begin
  GetText := FormatDateTime ('dddddd', fDate);
end;
Tip 

Using regional information, the NewDate program automatically adapts itself to different Windows user settings. If you run this program on a computer with regional settings referring to a language other than English, it will automatically show month names in that language. To test this behavior, you just need to change the regional settings. Notice that regional-setting changes immediately affect the running programs.

Once you have defined the new class, you need to use this new data type in the code of the form of the NewDate example, defining the TheDay object of type TNewDate and creating an object of this new class in the FormCreate method. You don't have to change the code with method calls, because the inherited methods still work exactly in the same way; however, their effect changes, as the new output demonstrates (see Figure 2.6).


Figure 2.6: The output of the NewDate program, with the name of the month and of the day depending on Windows regional settings

Protected Fields and Encapsulation

The code of the GetText method of the TNewDate class compiles only if it is written in the same unit as the TDate class. In fact, it accesses the fDate private field of the ancestor class. If we want to place the descendant class in a new unit, we must either declare the fDate field as protected or add a protected access method in the ancestor class to read the value of the private field.

Many developers believe that the first solution is always the best, because declaring most of the fields as protected will make a class more extensible and will make it easier to write inherited classes. However, this approach violates the idea of encapsulation. In a large hierarchy of classes, changing the definition of some protected fields of the base classes becomes as difficult as changing some global data structures. If 10 derived classes are accessing this data, changing its definition means potentially modifying the code in each of the 10 classes.

In other words, flexibility, extension, and encapsulation often become conflicting objectives. When this happens, you should try to favor encapsulation. If you can do so without sacrificing flexibility, that will be even better. Often this intermediate solution can be obtained by using a virtual method, a topic I'll discuss in detail in the section "Late Binding and Polymorphism." If you choose not to use encapsulation in order to obtain faster coding of the inherited classes, then your design might not follow the object-oriented principles.

Inheritance and Type Compatibility

Pascal is a strictly typed language. This means that you cannot, for example, assign an integer value to a Boolean variable, unless you use an explicit typecast. The rule is that two values are type-compatible only if they are of the same data type, or (to be more precise) if their data type refers to a single type definition. To simplify your life, Delphi makes some predefined types assignment compatible: you can assign an Extended to a Double and vice versa, with automatic promotion or demotion (and potential accuracy loss).

Warning 

If you redefine the same data type in two different units, the types won't be compatible, even if their names are identical. A program using two equally named types from two different units will be a nightmare to compile and debug.

There is an important exception to this rule in the case of class types. If you declare a class, such as TAnimal, and derive from it a new class, say TDog, you can then assign an object of type TDog to a variable of type TAnimal. You can do so because a dog is an animal! As a general rule, you can use an object of a descendant class any time an object of an ancestor class is expected. However, the reverse is not legal; you cannot use an object of an ancestor class when an object of a descendant class is expected. To simplify the explanation, here it is again in code terms:

var
  MyAnimal: TAnimal;
  MyDog: TDog;
begin
  MyAnimal := MyDog;  // This is OK
  MyDog := MyAnimal;  // This is an error!!!

 
Previous Section Next Section


 


 

Delphi Sources


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