TBase というクラスがあり、以下のように定義されていたとします。
TBase = class(TObject) private FNumber: Integer; public procedure GetNumber; procedure SetNumber(num: Integer); end;
次に、この TBase クラスから派生した Derived クラスを定義します。
TDerived = class(TBase) end;
クラス TDerived は、フィールドやメソッドを明示的に定義していませんが、既に上位クラス TBase からフィールド FNumber、メソッド GetNumber, SetNumer を引き継いでいます。
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private 宣言 } public { Public 宣言 } end; TBase = class(TObject) private FNumber: Integer; public procedure GetNumer; procedure SetNumber(num: Integer); end; TDerived = class(TBase) end; var Form1: TForm1; implementation {$R *.dfm} { TBase } procedure TBase.GetNumer; begin ShowMessage(IntToStr(FNumber)); end; procedure TBase.SetNumber(num: Integer); begin FNumber := num; end; procedure TForm1.Button1Click(Sender: TObject); var Derived: TDerived; begin Derived := TDerived.Create; Derived.SetNumber(123); Derived.GetNumer; Derived.Free; end; end.
またとても重要なことですが、上位クラス型の変数に下位オブジェクトを代入する事が可能です。
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private 宣言 } public { Public 宣言 } end; TBase = class(TObject) private FNumber: Integer; public procedure GetNumer; procedure SetNumber(num: Integer); end; TDerived = class(TBase) end; var Form1: TForm1; implementation {$R *.dfm} { TBase } procedure TBase.GetNumer; begin ShowMessage(IntToStr(FNumber)); end; procedure TBase.SetNumber(num: Integer); begin FNumber := num; end; procedure TForm1.Button1Click(Sender: TObject); var Base: TBase; begin Base := TDerived.Create; Base.SetNumber(123); Base.GetNumer; Base.Free; end; end.
このように上位オブジェクト型の変数に下位オブジェクトが代入可能です。最初に言いましたように継承したクラスは、その上位クラスからフィールド、メソッドを引き継ぐと説明しました。それでは継承したクラスの中で、明示的に上位クラスから引き継いだメソッドを再び宣言した場合はどうなるのでしょうか。その例を見てみましょう。
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private 宣言 } public { Public 宣言 } end; TBase = class(TObject) public procedure ShowClassName; end; TDerived = class(TBase) public // ShowClassName は TBase クラスから継承されていますが、 // 継承先のクラスで再び宣言をしてみます。 procedure ShowClassName; end; var Form1: TForm1; implementation {$R *.dfm} { TBase } procedure TBase.ShowClassName; begin ShowMessage('Base'); end; { TDerived } procedure TDerived.ShowClassName; begin ShowMessage('Derived'); end; procedure TForm1.Button1Click(Sender: TObject); var Base: TBase; Derived: TDerived; begin Base := TBase.Create; Base.ShowClassName; // TBase クラスの ShowClassName が呼ばれる Base.Free; Derived := TDerived.Create; Derived.ShowClassName; // TDerived クラスの ShowClassName が呼ばれる Derived.Free; Base := TDerived.Create; Base.ShowClassName; // どっちの ShowClassName が呼ばれるのか? Base.Free; end; end.
上位クラス型の変数に下位オブジェクトを代入し、メソッド ShowClassName を呼び出したところ「Base」と表示されました。このように継承されているメソッドを下位クラスで明示的に宣言した場合には、どのような仕組みでメソッドの呼び出しが行われるのかを知る必要があります。
クラスのメソッドは、静的(これがデフォルトです。非仮想とも呼ばれます)、仮想、動的として宣言する事ができます。静的メソッドの場合、そのメソッドの呼び出しで使用された変数の宣言時の型によって、どのメソッドが呼び出されるかが決定されます。先ほどの例をもう一度詳しく見てみます。
TBase = class(TObject) public procedure ShowClassName; // 静的メソッド end; TDerived = class(TBase) public procedure ShowClassName; // 静的メソッド end; .................................. procedure TForm1.Button1Click(Sender: TObject); var // Base は TBase 型の変数として宣言 Base: TBase; // Derived は TDerived 型の変数として宣言 Derived: TDerived; begin Base := TBase.Create; // Base 変数からメソッド ShowClassName が呼ばれる // ShowClassName は静的メソッドであるので、 // TBase クラスで定義してある ShowClassName が呼ばれる Base.ShowClassName; Base.Free; Derived := TDerived.Create; // Derived 変数からメソッド ShowClassName が呼ばれる // ShowClassName は静的メソッドであるので、 // TDerived クラスで定義してある ShowClassName が呼ばれる Derived.ShowClassName; Derived.Free; Base := TDerived.Create; // Base 変数からメソッド ShowClassName が呼ばれる // ShowClassName は静的メソッドであるので、 // TBase クラスで定義してある ShowClassName が呼ばれる // なぜなら Base 変数は、TBase 型の変数として宣言されているので。 Base.ShowClassName; Base.Free; end; end.
このように静的メソッドが呼ばれる場合には、そのメソッド呼び出しの際に使用される変数の宣言時の型で、どのメソッドが呼ばれるのかが決定されています。
仮想メソッド、動的メソッドを宣言する場合には、その宣言時にそれぞれキーワード virtual, dynamic を付けてやります。この仮想メソッド、動的メソッドは意味的には2つとも等価ですので、ここでは仮想メソッドについてだけ取り上げていきます。
仮想メソッドを指定する事で静的メソッドと何に違いがあるのかというと、それは上位クラス型変数に、下位オブジェクトを代入した場合のメソッド呼び出し時にあります。仮想メソッドや動的メソッドは下位クラスでオーバーライドする事が出来ます。そしてオーバーライドされたメソッドが呼び出される際には、メソッド呼び出し時に使用される変数の宣言時の型ではなく実際に代入されたオブジェクトの型によって、どのメソッドが呼ばれるかが決定します。(オーバーライドとは、上位クラスから継承されたメソッドを下位クラスで再定義することを指します。これは、上位クラスでの実装を下位クラスでまるごと置き換えることを意味しているので、どのクラスのメソッドを呼び出すかにより動作をまるっきり変える事が可能となります!)メソッドをオーバーライドするには、キーワード override を使用してメソッドを再宣言します。
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private 宣言 } public { Public 宣言 } end; TBase = class(TObject) public procedure ShowClassName; virtual; // 仮想メソッド end; TDerived = class(TBase) public procedure ShowClassName; override; // オーバーライド end; var Form1: TForm1; implementation {$R *.dfm} { TBase } procedure TBase.ShowClassName; begin ShowMessage('Base'); end; { TDerived } procedure TDerived.ShowClassName; begin ShowMessage('Derived'); end; procedure TForm1.Button1Click(Sender: TObject); var Base: TBase; Derived: TDerived; begin // TBase 型の変数 Base に TBase 型のオブジェクトを代入 Base := TBase.Create; // 呼び出されるメソッドは、Base に代入されているオブジェクトの型のメソッド // よって、TBase 型で宣言されている ShowClassName が呼ばれる Base.ShowClassName; Base.Free; // TDerived 型の変数 Derived に TDerived 型のオブジェクトを代入 Derived := TDerived.Create; // 呼び出されるメソッドは、Derived に代入されているオブジェクトの型のメソッド // よって、TDerived 型で宣言されている ShowClassName が呼ばれる Derived.ShowClassName; Derived.Free; // TBase 型の変数 Base に TDerived 型のオブジェクトを代入 Base := TDerived.Create; // 呼び出されるメソッドは、Base に代入されているオブジェクトの型のメソッド // よって、TDerived 型で宣言されている ShowClassName が呼ばれる Base.ShowClassName; Base.Free; end; end.
注意しなければならないのは、オーバーライドできるのは仮想メソッド、動的メソッドだけです。静的メソッドはオーバーライド出来ません。