打印本文 打印本文 关闭窗口 关闭窗口
The Delphi Object Model (PART I)
作者:武汉SEO闵涛  文章来源:敏韬网  点击数4187  更新时间:2009/4/23 18:43:49  文章录入:mintao  责任编辑:mintao
ived classes must override that method. The C++ term for an abstract method is a "pure virtual method." If you call a constructor for a class that has an abstract method, the compiler issues a warning, telling you that you probably made a mistake. You probably wanted to create an instance of a derived class that overrides and implements the abstract method. A class that declares one or more abstract methods is often called an abstract class, although some people reserve that term for a class that declares only abstract methods.

TIP:
If you write an abstract class that inherits from another abstract class, you should redeclare all abstract methods with the override and abstract directives. Delphi does not require this, but common sense does. The declarations clearly inform the maintainer of the code that the methods are abstract. Otherwise, the maintainer must wonder whether the methods should have been implemented or should have remained abstract. For example:

type
  TBaseAbstract = class
    procedure Method; virtual; abstract;
  end;
  TDerivedAbstract = class(TBaseAbsract)
    procedure Method; override; abstract;
  end;
  TConcrete = class(TDerivedAbstract)
    procedure Method; override;
  end; 

A class method or constructor can also be virtual. In Delphi, class references are real entities that you can assign to variables, pass as parameters, and use as references for calling class methods. If a constructor is virtual, a class reference can have a static type of the base class, but you can assign to it a class reference for a derived class. Delphi looks up the virtual constructor in the class''''s VMT and calls the constructor for the derived class.

Methods (and other functions and procedures) can be overloaded, that is, multiple routines can have the same name, provided they take different arguments. Declare overloaded methods with the overload directive. A derived class can overload a method it inherits from a base class. In that case, only the derived class needs the overload directive. After all, the author of the base class cannot predict the future and know when other programmers might want to overload an inherited method. Without the overload directive in the derived class, the method in the derived class hides the method in the base class, as shown in Example 2-6.

Example 2-6: Overloading Methods

type
  TAuditKind = (auInternal, auExternal, auIRS, auNasty);
  TAccount = class
  public
    procedure Audit;
  end;
  TCheckingAccount = class(TAccount)
  public
    procedure Audit(Kind: TAuditKind); // Hides TAccount.Audit
  end;
  TSavingsAccount = class(TAccount)
  public
    // Can call TSavingsAccount.Audit and TAccount.Audit
    procedure Audit(Kind: TAuditKind); overload;
  end;
var
  Checking: TCheckingAccount;
  Savings: TSavingsAccount;
begin
  Checking := TCheckingAccount.Create;
  Savings := TSavingsAccount.Create;
  Checking.Audit;             // Error because TAccount.Audit is hidden
  Savings.Audit;              // Okay because Audit is overloaded
  Savings.Audit(auNasty);     // Okay
  Checking.Audit(auInternal); // Okay

Constructors

Every class has one or more constructors, possibly inherited from a base class. By convention, constructors are usually named Create, although you can use any name you like. Some constructor names start with Create, but convey additional information, such as CreateFromFile or CreateFromStream. Usually, though, the simple name Create is sufficient, and you can use method overloading to define multiple constructors with the same name. Another reason to overload the name Create is for compatibility with C++ Builder. C++ does not permit different constructor names, so you must use overloading to define multiple constructors.

Calling a constructor

A constructor is a hybrid of object and class methods. You can call it using an object reference or a class reference. Delphi passes an additional, hidden parameter to indicate how it was called. If you call a constructor using a class reference, Delphi calls the class''''s NewInstance method to allocate a new instance of the class. After calling NewInstance, the constructor continues and initializes the object. The constructor automatically sets up a try-except block, and if any exception occurs in the constructor, Delphi calls the destructor.

When you call a constructor with an object reference, Delphi does not set up the try-except block and does not call NewInstance. Instead, it calls the constructor the same way it calls any ordinary method. This lets you call an inherited constructor without unnecessary overhead.

TIP:
A common error is to try to create an object by calling a constructor with an object reference, rather than calling it with a class reference and assigning it to the object variable:

var
  Account: TSavingsAccount;
begin
  Account.Create;                    // wrong
  Account := TSavingsAccount.Create; // right

One of Delphi''''s features is that you have total control over when, how, and whether to call the inherited constructor. This lets you write some powerful and interesting classes, but also introduces an area where it is easy to make mistakes.

Delphi always constructs the derived class first, and only if the derived class calls the inherited constructor does Delphi construct the base class. C++ constructs classes in the opposite direction, starting from the ancestor class and constructing the derived class last. Thus, if class C inherits from B, which inherits from A, Delphi constructs C first, then B, and A last. C++ constructs A first, then B, and finally C.

Virtual methods and constructors

Another significant difference between C++ and Delphi is that in C++, a constructor always runs with the virtual method table of the class being constructed, but in Delphi, the virtual methods are those of the derived class, even when the base class is being constructed. As a result, you must be careful when writing any virtual method that might be called from a constructor. Unless you are careful, the object might not be fully constructed when the method is called. To avoid any problems, you should override the AfterConstruction method and use that for any code that needs to wait until the object is fully constructed. If you override AfterConstruction, be sure to call the inherited method, too.

One constructor can call another constructor. Delphi can tell the call is from an object reference (namely, Self), so it calls the constructor as an ordinary method. The most common reason to call another constructor is to put all the initialization code in a single constructor. Example 2-7 shows some different ways to define and call constructors.

Example 2-7: Declaring and Calling Constructors

type
  TCustomer = class ... end;
  TAccount = class
  private
    fBalance: Currency;
    fNumber: Cardinal;
    fCustomer: TCustomer;
  public
    constructor Create(Customer: TCustomer); virtual;
    destructor Destroy; override;
  end;
  TSavingsAccount = class(TAccount)
  private
    fInterestRate: Integer; // Scaled by 1000
  public
    constructor Create(Customer: TCustomer); override; overload;
    constructor Create(Customer: TCustomer; InterestRate: Integer);
        overload;
    // Note that TSavingsAccount does not need a destructor. It simply
    // inherits the destructor from TAccount.
  end;
 
var
  AccountNumber: Cardinal = 1;
 
constructor TAccount.Create(Customer: TCustomer);
begin
  inherited Create;             // Call TObject.Create.
  fNumber := AccountNumber;     // Assign a unique account number.
  Inc(AccountNumber);
  fCustomer := Customer;        // Notify customer of new account.
  Customer.AttachAccount(Self);
end;
 
destructor TAccount.Destroy;
begin
  // If the constructor fails before setting fCustomer, the field
  // will be nil. Release the account only if Customer is not nil.
  if Customer <> nil then
    Customer.ReleaseAccount(Self);
  // Call TObject.Destroy.
  inherited Destroy;
end;
 
const
  DefaultInterestRate = 5000;  // 5%, scaled by 1000
 
constructor TSavingsAccount.Create(Customer: TCustomer);
begin
  // Call a sibling constructor.
  Create(Customer, DefaultInterestRate);
end;
 
constructor TSavingsAccount(Customer: TCustomer; InterestRate:Integer);
begin
  // Call TAccount.Create.
  inherited Create(Customer);
  fInterestRate := InterestRate;
end;

Destructors

Destructors, like constructors, take an extra hidden parameter. The first call to a destructor passes True for the extra parameter. This tells Delphi to call FreeInstance to free the object. If the destructor calls an inherited destructor, Delphi passes False as the hidden parameter to prevent the inherited destructor from trying to free the same object.

TIP:
A class usually has one destructor, called Destroy. Delphi lets you declare additional destructors, but you shouldn''''t take advantage of that feature. Declaring multiple destructors is confusing and serves no useful purpose.

Before Delphi starts the body of the destructor, it calls the virtual method, BeforeDestruction. You can override BeforeDestruction to assert program state or take care of other business that must take place before any destructor starts. This lets you write a class safely without worrying about how or whether any derived classes will call the base class destructor.

TIP:
When writing a class, you might need to override the Destroy de

上一页  [1] [2] [3] [4] [5]  下一页

打印本文 打印本文 关闭窗口 关闭窗口