Difference between revisions of "Programming Using Objects/ja"

From Free Pascal wiki
Jump to navigationJump to search
 
Line 206: Line 206:
 
Calling Square.Draw : TSquare.Draw</pre>
 
Calling Square.Draw : TSquare.Draw</pre>
  
Tthe ''Shape'' object is initialized by calling the ''GetParams'' method and then the ''Draw'' method is called which prints out the field values.  Next, the same is done for the ''Rectangle'' object.  Notice that since there is no ''GetParams'' method for Rectangles, the compiler used the parent object's method, ''TShape.GetParams'' to carry out this action.  Finally, the ''Square'' object is initialized by calling the ''GetParams'' method which is explicitly defined for ''Squares'' and the output shows this method was called and did extra processing.  The ''Draw'' method is called and the ''Draw'' method specifically defined for ''Squares'' was executed.  Note that if the ''Draw'' procedure was left out for the ''TSquare'' type (as would be reasonable for an actual program) the last output line would look like this.
+
''Shape''オブジェクトは、''GetParams''メソッドを呼び出して初期化され、その後''Draw''メソッドが呼び出され、フィールド値が表示される。次に、同じことが''Rectangle''オブジェクトでも行われる。長方形には''GetParams''メソッドがないため、コンパイラは親オブジェクトのメソッドである''TShape.GetParams''を使用してこのアクションを実行する。最後に、''Square''オブジェクトが''Squares''のために明示的に定義された''GetParams''メソッドを呼び出して初期化され、そのメソッドが呼び出されて追加の処理が行われたことが出力される。次に、''Draw''メソッドが呼び出され、特に''Squares''のために定義された''Draw''メソッドが実行されます。なお、''TSquare''タイプの''Draw''手順が省略されている場合(実際のプログラムでは合理的である)、最後の出力行は次のようになる。
  
 
<pre>Calling Square.Draw : TRectangle.Draw</pre>
 
<pre>Calling Square.Draw : TRectangle.Draw</pre>
  
Now what happens if we assign a sub object variable to a parent variable as follows?  
+
さて、以下のように、サブオブジェクト変数を親変数に割り当てると何が起こるだろうか?  
  
 
<syntaxhighlight lang="pascal">
 
<syntaxhighlight lang="pascal">
Line 251: Line 251:
 
Calling Rectangle.Draw : TRectangle.Draw</pre>
 
Calling Rectangle.Draw : TRectangle.Draw</pre>
  
The first block of code assigns the ''Rectangle'' variable to the ''Shape'' variable.  When the ''Shape.Draw'' method is invoked, it executes the ''TShape.Draw'' code and not the ''TRectangle.Draw'' code.  But as can be seen, the ''Rectangle'' fields is what are printed out.  This behavior is called '''Static''' method inheritance in FPC.  If it is desired to instead invoke the child ''TRectangle.Draw'' method in this situation, FPC provides way to do this called '''virtual''' methods which will be covered in the next section.  Continuing on, ''Shape'' is assigned to ''Square'' and the ''TShape.Draw'' method is invoked which prints out the field values of the ''Square'' object.  Finally, the Rectangle variable is assigned the Square and the ''TRectangle.Draw'' method is invoked similarly to the behavior of the ''TShape.Draw'' methods.
+
最初のコードブロックでは、''Rectangle''変数を''Shape''変数に割り当てる。 ''Shape.Draw''メソッドが呼び出されると、''TShape.Draw''コードが実行され、''TRectangle.Draw''コードは実行されない。 しかし、''Rectangle''のフィールドが出力される。この動作は、FPCの '''Static'''メソッド継承と呼ばれる。 代わりにこの状況で子の ''TRectangle.Draw''メソッドを呼び出す場合は、FPCは '''virtual'''メソッドと呼ばれる方法を提供している。これについては次のセクションで説明する。続いて、''Shape''''Square''に割り当てられ、''TShape.Draw''メソッドが呼び出され、''Square''オブジェクトのフィールド値が出力される。 最後に、Rectangle変数にSquareが割り当てられ、''TShape.Draw''メソッドの動作は、 ''TShape.Draw''メソッドの振る舞いと同様に ''TRectangle.Draw''メソッドが呼び出される。
  
Assigning an ancestor object to a descendent object is not allowed.  The compiler will flag the following line as an error.
+
祖先オブジェクトを派生オブジェクトに割り当てることは許可されていない。コンパイラは次の行をエラーとしてフラグ付けします。
  
<syntaxhighlight lang="pascal">Rectangle := Shape;  // can not assign a parent to a child, does not compile</syntaxhighlight>
+
<syntaxhighlight lang="pascal">Rectangle := Shape;  //子に親を割り当てることはできない、コンパイルできない</syntaxhighlight>
  
 
== オブジェクト - 実質的継承 ==
 
== オブジェクト - 実質的継承 ==

Latest revision as of 15:25, 3 April 2024

オブジェクト - 基本

FPCには、オブジェクトクラスの2つのOOP実装が提供されている。Objectsの主要なドキュメントは、FPC言語リファレンスの第5章にあり、Classesのドキュメントは第6章にある。このチュートリアルでは、あまり使われない「Objects」実装について説明している。

Objectタイプは、追加のmethodフィールドとフィールドのスコープを示すオプションのキーワードを持つrecordタイプに似ている。メソッドのないオブジェクトの宣言は、レコードと区別するのが難しい場合がある。

Type
   MyObject = Object
      f_Integer : integer;
      f_String : ansiString;
      f_Array : array [1.3] of char;
   end;

上記の例では、Pascalのキーワードrecordがキーワードobjectに置き換えられている。オブジェクトは、methodフィールドがオブジェクトに追加されるとより有用である。FPCでのオブジェクトメソッドは、procedureまたはfunctionのキーワードを使用して宣言され、通常のPascal手続きや関数と同じ方法で宣言されますが、オブジェクト宣言のスコープ内で宣言される。より有用なオブジェクト宣言(グラフィックアプリケーションの一部として)を以下に示す。

Type
   DrawingObject = Object
      x, y : single;
      height, width : single;
      procedure Draw;
   end;

Var
  Rectangle : DrawingObject;

上記の例では、単精度浮動小数点データフィールドに加えて、メソッドも宣言されている。Drawと呼ばれる手続きです。"DrawingObject"型の宣言の後に、Rectangleという名前の変数が宣言されている。これはDrawingObject型です。次に、Draw手続きのコード自体を記述する必要がある。また、データフィールドにアクセスして操作するコードも必要である。Draw手続きは、DrawingObject宣言の外部に別個に書かれる。この手続き宣言がどこに書かれるかは指定されておらず、特定のコーディングスタンダードに依存する。

以下のシンプルなプログラムは、これがどのように機能するかを示している。FPC 2.2.2以降のシステムでコンパイルおよび実行できるはずだ。注意: macOSの場合、-macpasコンパイラディレクティブをオフにする必要がある。

Program TestObjects;

Type
   DrawingObject = Object
      x, y : single;
      height, width : single;
      procedure Draw;  //  プロシージャがここで宣言されている。
   end;

  procedure DrawingObject.Draw;
  begin
       writeln('Drawing an Object');
       writeln(' x = ', x, ' y = ', y);  // オブジェクトフィールド
       writeln(' width = ', width);
       writeln(' height = ', height);
       writeln;
    //    moveto (x, y);  // おそらく実際の描画を行うためにはここでプラットフォーム依存の描画ユニットを含める必要がある。
    //    ... 他のパラメータを用い画面に実際の描画をするさらなるコード。
  end;

Var
  Rectangle : DrawingObject;

begin

  Rectangle.x:= 50;  //  変数「四角形」に特異的なフィールド
  Rectangle.y:= 100;
  Rectangle.width:= 60;
  Rectangle.height:= 40;

  writeln('x = ', Rectangle.x);

  Rectangle.Draw;  //  メソッド(プロシージャ)を呼ぶ

  with Rectangle do   // メソッド(プロシージャ)フィールドに対する同様の仕事
   begin
       x:= 75;
       Draw;
       readln; 
   end;

end.

上記のプログラムでは、Drawメソッド(手続き)の本体は、オブジェクト型の宣言の後に、型識別子と手続き名を連結して宣言されている。より現実的な状況では、オブジェクトは別のユニットのinterfaceセクションで宣言される可能性が高いが、手続き本体は同じ外部ユニットのimplementationセクションに記述されるだろう。この例では、標準の「素の」Pascalのみを使用しているが、実際には特定のグラフィックプリミティブが使用されてオブジェクトを描画するだろう。もう1つ気づくことは、Draw手続きメソッドの内部で、オブジェクトのデータフィールドが通常のローカル変数のように参照されていることである。通常のローカル変数との唯一の違いは、これらのフィールドの値がDraw手続きへの呼び出しの間に一貫していることである。

メインプログラムでは、フィールドに値が代入され、レコードフィールドが代入されるのと同じようにアクセスされる。同様に、Draw手続きは、フィールドと同じドット表記を使用して呼び出される。また、レコードと同様に、withキーワードを使用すると、オブジェクトのフィールドへのアクセスとメソッドの呼び出しが同じように動作する。Drawメソッドが2回目に呼び出された際に、すべてのフィールドが呼び出しの間に保持されたことに注意すること。唯一異なるフィールドは、明示的に変更されたx属性である。

上記の例は、単純な オブジェクト の基本的な仕組みを示すものである。しかし、いくつかの問題がある。第一の問題は、Drawメソッドが1つのものだけを描画することである:四角形(描画プリミティブが利用可能な場合)。追加のメソッドは、DrawingObjectオブジェクトにDrawRectangle、DrawCircle、DrawTriangleなどが宣言されるかもしれないが、これは単に個別の標準プロシージャを宣言し、所望のプロシージャを選択するためにcase文を使用するのとあまり変わらない。したがって、このようにオブジェクトを実装するには、標準のパスカルを使用するよりも多くの作業が必要である。このオブジェクトのフィールドは、プログラム全体でグローバルにアクセスおよび変更できるため、しっかりとカプセル化された堅牢なプログラムを作成するという欲求を回避している。これらの問題は、次のセクションで解決される。

オブジェクト - 静的継承

次に、コードは四角形に加えて正方形を含めるように拡張される。他の形状も同様の方法で含めることができる。可能であれば、既存のコードを活用するために、2つの特定の形状を処理する新しいオブジェクトを作成する。コードと出力を理解しやすくするために、メインオブジェクトタイプである TShapeDrawingObject から TShape に名前が変更された。他のオブジェクトタイプは TRectangleTSquare と呼ばれ、それに対応する変数は Shape、Rectangle、Square となる。"T" は、FPCを含む多くのオブジェクトライブラリやフレームワークで一般的に使用される接頭辞であるため、オブジェクトタイプ名のプレフィックスとして使用されている。

以下に、メインの TShape オブジェクトタイプ(以前の DrawingObject)と新しいタイプ TRectangleTSquare の型宣言が続く。 TShape には新しいメソッド(procedure GetParams)が含まれてる。次に、 TRectangleTSquare のタイプが宣言される。 TShape は通常、祖先または親オブジェクトと呼ばれ、TRectangle は通常、 TShape の子オブジェクトまたはサブオブジェクトまたは TShape から派生したと言われることがある。この親/子/孫の階層は、Objectキーワードに続く修飾子タイプ名で識別されます。同様に、TSquare はTRectangleの子である。また、 TShape は実際には完全なオブジェクトタイプではなく、他のオブジェクトが共通の構造と動作を継承するためのテンプレートである。このようなテンプレートはコードの明確性のためにしばしば有用であり、これらのテンプレートオブジェクトの特定の特性を強制するために使用される言語機能もある。実際のアプリケーションでは、 TShape タイプはおそらく異なる方法で宣言されるだろう。ここでは、基本的な概念を示すために TShape が使用されている。

Type
  TShape = Object
    x, y : single;
    height, width : single;
    procedure GetParams;
    procedure Draw;
  end;

  TRectangle = Object(TShape)
    procedure Draw;
  end;

  TSquare = Object(TRectangle)
    procedure GetParams;
    procedure Draw;
  end;

Var
  Shape : TShape;
  Rectangle : TRectangle;
  Square : TSquare;

TRectangleDraw プロシージャのみをリストしており、TSquareGetParamsDraw プロシージャの両方をリストしていることに注意すること。いずれのサブオブジェクトタイプもフィールドを含んでいない。欠落しているフィールドとプロシージャ名は、祖先オブジェクトで宣言されたフィールドとプロシージャから継承されると言われる。この場合、 TRectangle タイプの宣言およびインスタンス化された任意のオブジェクト変数は、 TShape タイプからすべての四つのフィールド(x、y、height、width)と GetParams メソッドを継承する。実行時に、 TRectangle タイプの変数は、 TShape タイプの変数と同じように見え、振る舞うが、 Draw プロシージャは TShape タイプの変数とは異なるコードを使用する。同様に、 TSquare オブジェクトタイプは TShape から「祖父」のフィールドをすべて継承し、独自の異なるプロシージャブロックを持つ。必要に応じて、 TRectangleTSquare は、タイプ定義で追加のフィールドを宣言することができる。これにより、実行時に追加のフィールド(メモリロケーション)が表示され、インスタンス化された親オブジェクト変数が持たないまたはアクセスできない追加のフィールドが利用可能になる。

次に、これらのオブジェクトタイプの特定のプロシージャ実装が続く。

procedure TShape.GetParams;
begin
  write('TShape.GetParams : ');
  readln(x, y, width, height);
  writeln;
end;

procedure TShape.Draw;
begin
  writeln('TShape.Draw');
  writeln('Position: x = ', x:4:0, ' y = ', y:4:0);
  writeln('    Size: w = ', width:4:0, ' h = ', height:4:0);
  writeln;
end;

procedure TRectangle.Draw;
begin
  writeln('TRectangle.Draw');
end;

procedure TSquare.GetParams;
begin
  write('TSquare.GetParams : ');
  readln(x, y, width, height);
  height := width;
  writeln('making sure all sides are equal for Square');
  writeln;
end;

procedure TSquare.Draw;
begin
  writeln('TSquare.Draw');
end;

明確さを提供するために、実際の(プラットフォーム固有の)描画ルーチンは使用されていない。代わりに、実行時の動作を示すために汎用のPascal I/Oルーチンが含まれている。この特定のオブジェクト、フィールド、およびメソッドの階層は、新しい概念が導入された後、実際の形状描画アプリケーションのための最良の実装ではないだろう

GetParams メソッドは、フィールド値を取得し、必要な処理を実行するために使用される。以前のセクションで行われたように、オブジェクトフィールドに直接アクセスできるが、オブジェクトメソッドを使用してオブジェクトフィールドの「取得」および「設定」をカプセル化することがプログラミングスタイルとしてより良いとされている。実際、FPCには、内部オブジェクトデータのアクセス性と可視性を制限するための追加の言語機能が提供されており、後で説明する。

このプログラムの異なる種類のオブジェクトはすべて同じフィールドを使用するため、すべての他の派生オブジェクトが継承/使用できる最上位の祖先オブジェクトに GetParams メソッドを含めるのが合理的に思える。したがって、このメソッドは TShape オブジェクトタイプで実装されている。

TShape オブジェクトタイプには、2番目のメソッドである Draw が含まれていますが、これは定義された形状ではないため、実際の形状描画プログラムではおそらく必要ない。ただし、 TShape タイプの変数はおそらく TRectangle のような子孫オブジェクトタイプの変数に割り当てられるため、 TRectangle のコードを使用して描画する必要がある。通常、この「テンプレート」メソッドはスタブとしてだけ宣言されるか、まったく実装されない。このタイプの状況やその他の状況の構文については、後で説明する。この場合、フィールド値を検査し、プログラムのフローを示すいくつかの writeln ステートメントを含めることで、デモンストレーションの目的でいくつかのフィードバックを提供したいと考えている。

TRectangleオブジェクトタイプにはGetParamsメソッドは含まれていないが、Drawメソッドがある。TSquareオブジェクトタイプには、独自のGetParamsメソッドがある。このメソッドは、TShape.GetParamsメソッドから多くのコードを繰り返し使用するが、高さと幅のフィールドが同じであることを確認するコードも追加されている。TSquareは独自のDrawメソッドを定義する。実際のプログラムでは、正方形と長方形のためのDrawメソッドはおそらく同じであり、TSquareDrawメソッドは省略して、TRectangleから継承することができる。

以下は、さまざまなオブジェクトの動作を示すサンプルプログラムである:

Var
  Shape : TShape;
  Rectangle : TRectangle;
  Square : TSquare;
begin 
  writeln;
  writeln ('Getting parameters for Shape');
  Shape.GetParams;
  
  write ('Calling Shape.Draw : ');
  Shape.Draw;

  writeln;
  writeln ('Getting parameters for Rectangle');
  Rectangle.GetParams;

  write ('Calling Rectangle.Draw : ');
  Rectangle.Draw;
	
  writeln;
  writeln ('Getting parameters for Square');
  Square.GetParams;

  write ('Calling Square.Draw : ');
  Square.Draw;
  readln ;
end.

上記のコードは以下の出力をする。

Getting parameters for Shape
TShape.GetParams : 1 2 3 4
Calling Shape.Draw : TShape.Draw
Position: x =    1 y =    2
    Size: w =    3 h =    4


Getting parameters for Rectangle
TShape.GetParams : 11 22 33 44
Calling Rectangle.Draw : TRectangle.Draw

Getting parameters for Square
TSquare.GetParams : 111 222 333 444
making sure all sides are equal for Square

Calling Square.Draw : TSquare.Draw

Shapeオブジェクトは、GetParamsメソッドを呼び出して初期化され、その後Drawメソッドが呼び出され、フィールド値が表示される。次に、同じことがRectangleオブジェクトでも行われる。長方形にはGetParamsメソッドがないため、コンパイラは親オブジェクトのメソッドであるTShape.GetParamsを使用してこのアクションを実行する。最後に、SquareオブジェクトがSquaresのために明示的に定義されたGetParamsメソッドを呼び出して初期化され、そのメソッドが呼び出されて追加の処理が行われたことが出力される。次に、Drawメソッドが呼び出され、特にSquaresのために定義されたDrawメソッドが実行されます。なお、TSquareタイプのDraw手順が省略されている場合(実際のプログラムでは合理的である)、最後の出力行は次のようになる。

Calling Square.Draw : TRectangle.Draw

さて、以下のように、サブオブジェクト変数を親変数に割り当てると何が起こるだろうか?

writeln;
writeln ('Assigning Rectangle to Shape');
Shape := Rectangle;
writeln;
write ('Calling Shape.Draw : ');
Shape.Draw;
	
writeln;
writeln ('Assigning Square to Shape');
Shape := Square;
writeln;
write ('Calling Shape.Draw : ');
Shape.Draw;

writeln;
writeln ('Assigning Square to Rectangle');
Rectangle := Square;
writeln;
write ('Calling Rectangle.Draw : ');
Rectangle.Draw;

The following is output.

Assigning Rectangle to Shape

Calling Shape.Draw : TShape.Draw
Position: x =   11 y =   22
    Size: w =   33 h =   44

Assigning Square to Shape

Calling Shape.Draw : TShape.Draw
Position: x =  111 y =  222
    Size: w =  333 h =  333

Assigning Square to Rectangle

Calling Rectangle.Draw : TRectangle.Draw

最初のコードブロックでは、Rectangle変数をShape変数に割り当てる。 Shape.Drawメソッドが呼び出されると、TShape.Drawコードが実行され、TRectangle.Drawコードは実行されない。 しかし、Rectangleのフィールドが出力される。この動作は、FPCの Staticメソッド継承と呼ばれる。 代わりにこの状況で子の TRectangle.Drawメソッドを呼び出す場合は、FPCは virtualメソッドと呼ばれる方法を提供している。これについては次のセクションで説明する。続いて、ShapeSquareに割り当てられ、TShape.Drawメソッドが呼び出され、Squareオブジェクトのフィールド値が出力される。 最後に、Rectangle変数にSquareが割り当てられ、TShape.Drawメソッドの動作は、 TShape.Drawメソッドの振る舞いと同様に TRectangle.Drawメソッドが呼び出される。

祖先オブジェクトを派生オブジェクトに割り当てることは許可されていない。コンパイラは次の行をエラーとしてフラグ付けします。

Rectangle := Shape;   //子に親を割り当てることはできない、コンパイルできない

オブジェクト - 実質的継承

For a drawing application, a common task would be to refresh the display and step through an array or linked list of shapes and call the draw method. Instead of needing a case statement inside the loop which selects one of many possible specific drawing procedures

for k := 1 to NumShapes do
 with ShapeRec[k] do
  case ShapeRec[k].ShapeKind of
    cRectangle: DrawRectangle (x, y, width, height);
    cSquare:    DrawSquare (x, y, width, height);
    cTriangle:  DrawTriangle (x, y, angle1, angle2, base);
 end;  // case

the code would look something like this

for k:= 1 to Numshapes do
 Shape[k].draw;

Where each Shape object could be one of any sub objects descended from TShape. Code maintenance is made easier since there is one fewer locations in code which needs to be modified. All changes to the behavior of a particular sub object of shape is kept together in one place.

実質的キーワード

However, as seen in the last section, calling the Draw method for the Shape variable this way will not invoke the appropriate draw method of the sub object which is the behavior that is desired in this (and most) cases. To obtain the desired behavior, the virtual keyword must be inserted after the method declaration in the type definition as follows:

Type
  TShape = Object
    x, y : single;
    height, width : single;
    procedure GetParams;
    procedure Draw; virtual;
  end;
 
  TRectangle = Object(TShape)
    procedure Draw; virtual;
  end;

Now if a Rectangle object is assigned to the Shape variable, the Draw method of TRectangle will be used. The term often used to describe this situation is called overriding a parent method. Although the body of main program will the same as in the previous section, the execution behavior will be different. The virtual keyword tells the compiler to hold off fixing the specific procedure used and instead lets the binding of the method be determined at runtime dynamically.

By adding the virtual keyword to the type declarations of TShape, TRectangle and TSquare in the last section, the latter portion of the output would look as shown below. Note that in order to run the previous program using virtual methods, some other code needs to be added in order for the program to run. This additional code is described in the next section.

Assigning Rectangle to Shape

Calling Shape.Draw : TRectangle.Draw

Assigning Square to Shape

Calling Shape.Draw : TSquare.Draw

Assigning Square to Rectangle

Calling Rectangle.Draw : TSquare.Draw

Although it is allowed, mixing virtual methods and non virtual (static) methods in the inheritance hierarchy may result in behavior which is difficult to manage.

オブジェクト - コンストラクタ と ディストラクタ

Compiling the above example program after adding the virtual keywords will result in non fatal compiler warnings about missing constructors. Although the warnings can be ignored, a run time error will almost certainly occur when one of the virtual Draw methods is executed. Due to the peculiarities of this particular OOP implementation, when virtual methods are declared, special initialization code must be included for that object. Specifically, two specialized methods must be included in the object type definition called a constructor and a destructor. The constructor must be called at runtime to initialize the object's virtual method before the method is called. In addition, the constructor can be (and should) be used to initialize any fields, dynamically create associated objects and any other initialization tasks needed when introducing an object. The special destructor method is used to take care of any internal and program specific housekeeping when an object is no longer needed. The initialization and cleanup tasks are more useful when using dynamically allocated objects which will be covered in this section also. For simple programs with few objects (like the one in this tutorial), calling not using destructors won't cause any problems. However, in large programs and those that use large class libraries which routinely allocate and deallocate objects dynamically, the implementation of constructors and destructors is very useful.

Here are are the Shape declarations again, this time using virtual methods and including constructors and destructors.

Type
  TShape = Object
    x, y : single;
    height, width : single;

    procedure GetParams; virtual;
    procedure Draw; virtual;

    Constructor Init(xx, yy, h, w : single);
    Destructor CleanUp;
  end;

  TRectangle = Object(TShape)
    procedure Draw; virtual;
  end;

  TSquare = Object(TRectangle)
    procedure GetParams; virtual;
 
    Constructor Init(xx, yy, h, w : single);
  end;

The TShape object type includes fields, two virtual methods (Draw and GetParams), a constructor called Init and a destructor called CleanUp.

TRectangle declares its own Draw method which will override the Parent method in TShape but will inherit all other fields, methods, and the constructor and destructor from TShape.

TSquare inherits everything from its ancestors (TShape mostly) except for the GetPArams method and the Init constructor.

In declaring a constructor or destructor, the keyword constructor or destructor is used instead of the keyword function or procedure. In all other respects, they look just like object methods and are called just like object methods. The use of the constructor/destructor keyword is to let the compiler know of any behind the scenes actions to take. Also notice, that the Init constructor includes a parameter list. Normal methods can include parameter lists although none have been used in the tutorial up to this point.

Fields, methods, constructors and destructors can be declared in any order in the type definition.

Constructors and destructors act like virtual methods without having to add the virtual keyword.

For the current program, the Draw method for TSquare has been removed. It will be assumed that the (fictional) graphics code for for drawing a square is the same as for a rectangle and the Draw method can be inherited from TRectangle. The difference in the TSquare and TRectangle object is inherent in the Init constructor and GetParams method. Implementation of the new constructors and destructors is shown below.

Constructor TShape.Init(xx, yy, h, w : single);
begin
  writeln('TShape.Init');
  x := xx;
  y := yy;
  height := h;
  width := w;
end;
  
Destructor TShape.CleanUp;
begin
  writeln('TShape.CleanUp')
end;

Constructor TSquare.Init(xx, yy, h, w : single);
begin
  writeln('TSquare.Init');
  x := xx;
  y := yy;
  height := h;
  width := w;
  if height <> width then
    height := width;
end;

Again, the implentation of constructors look like regular methods except the keywords constructor and destructor are used instead of the keywords procedure and function. Consider the following main program.

begin
  writeln;
  
  Shape.Init(1,2,3,4);
  Rectangle.Init(11, 22, 33, 44);
  Square.Init(111, 222, 333, 444);
 
  writeln;
  write ('Calling Shape.Draw : ');
  Shape.Draw;

  write ('Calling Rectangle.Draw : ');
  Rectangle.Draw;
	
  write ('Calling Square.Draw : ');
  Square.Draw;
	
  writeln;
  writeln ('Assigning Rectangle to Shape');
  Shape := Rectangle;
  writeln;
  write ('Calling Shape.Draw : ');
  Shape.Draw;
	
  writeln;
  writeln ('Assigning Square to Shape');
  Shape := Square;
  writeln;
  write ('Calling Shape.Draw : ');
  Shape.Draw;

  writeln;
  writeln ('Assigning Square to Rectangle');
  Rectangle := Square;
  writeln;
  write ('Calling Rectangle.Draw : ');
  Rectangle.Draw;
	
  writeln;
	
  Shape.CleanUp;
  Rectangle.CleanUp;
  Square.CleanUp;
  readln;
end.

which produces the output below.

TShape.Init
TShape.Init
TSquare.Init

Calling Shape.Draw : TShape.Draw
Position: x =    1 y =    2
    Size: w =    4 h =    3

Calling Rectangle.Draw : TRectangle.Draw
Calling Square.Draw : TSquare.Draw

Assigning Rectangle to Shape

Calling Shape.Draw : TRectangle.Draw

Assigning Square to Shape

Calling Shape.Draw : TSquare.Draw

Assigning Square to Rectangle

Calling Rectangle.Draw : TSquare.Draw

TShape.CleanUp
TShape.CleanUp
TShape.CleanUp

Notice that all three calls to the destructor CleanUp result in the using the inherited destructor of TShape since TRectangle and TSquare did not override the CleanUp constructor. The virtual Draw method was overridden for each sub object type and the output reflects this even when the sub objects were assigned to the parent object. Finally, since none of the sub objects implemented destructors, all calls to the CleanUp destructors used the one declared for the parent TShape object. If any of the destructors were declared separately in a sub object, they would have overridden the destructor for TShape since all constructors and destructors are virtual by default.

FPC allows any procedure identifier to be used for a constructor or destructor name. However, by convention, many OOP languages and object libraries use specific identifiers. One convention is Create for constructors and Destroy for destructors.

オブジェクト - 動的変数

Although static object variables can be created on the stack as has been shown up to this point, it is much more likely that for most programs, objects will be created dynamically. The syntax for declaring dynamic objects and invoking them are similar to that of any other dynamically created variable. The only difference is the addition of an extended syntax for the New keyword which incorporates invoking the object constructor.

Here, pointer types and variables are declared for the various object types defined previously with some sample code snippets showing how the resulting dynamic variables are created, manipulated and disposed. Only the object declaration for TShape is shown and none of the methods, constructors or destructors. Note that in order to inspect the field values more easily, the Draw method was reverted back to static so the TShape.Draw behavior was available for all the different types of objects.

The first thing to notice that there are three different ways to create dynamic object variables. All three produce the same results. All use the new procedure in different ways. The Shape1 and Shape2 objects are being created using new as a function, passing it two parameters: (1) the type name and (2) the name of the Init constructor and returning a pointer to the object. The second way is using new as a procedure with the desired variable (Rectangle in this case) to be to be created and the constructor name as parameters. Finally, a pointer for the Square object is created with the new procedure and then the constructor method is called separately. This last manner of dynamic object creation will generate a compiler warning but will compile and run OK.

Next some assignments with objects are made and the Draw method to see how the objects were affected. As can be seen, the results of the assignment operations differ depending whether or not the assignments were done with the pointer variables themselves or whether the pointer was dereferenced. The results are the same for manipulating and dereferencing pointers to objects just as they are for any other type of data structure. Finally, the dispose procedure is called for all the created objects. Just as there are three ways to use the new procedure, there are three ways to use the dispose procedure. All three ways are shown.

Type
  TShape = Object
    x, y : single;
    height, width : single;
    procedure GetParams; virtual;
    procedure Draw;
    Constructor Init(xx, yy, h, w : single);
    Destructor CleanUp;
  end;
  
  PShape = ^TShape;
  PRectangle = ^TRectangle;
  PSquare = ^TSquare;

Var
  Shape1, Shape2 : PShape;
  Rectangle : PRectangle;
  Square : PSquare;

begin
  Shape1 := new (PShape, Init(1, 1, 1, 1) );
  Shape2 := new (PShape, Init(2, 2, 2, 2) );

  new (Rectangle, Init(11, 22, 33, 44) ); 

  new(Square);
  Square^.Init(111, 222, 333, 444);

  writeln;

  Write ('1) Shape1 : ');
  Shape1^.Draw;

  Shape1^ := Rectangle^;
  Write ('2) Shape1 : ');
  Shape1^.Draw;

  Rectangle^.x := 77;
  Write ('3) Shape1 : ');
  Shape1^.Draw;

  Write ('4) Shape2 : ');
  Shape2^.Draw;

  Shape2 := Square;
  Write ('5) Shape2 : ');
  Shape2^.Draw;

  Square^.y := 88;
  Write ('6) Shape2 : ');
  Shape2^.Draw;

  writeln;

  dispose(Shape1);
  dispose(Shape2, CleanUp);

  Rectangle^.CleanUp;
  dispose(Rectangle);

  dispose(Square, CleanUp);
  readln;
end.
TShape.Init
TShape.Init
TShape.Init
TSquare.Init

1) Shape1 : TShape.Draw
Position: x =    1 y =    1
    Size: w =    1 h =    1

2) Shape1 : TShape.Draw
Position: x =   11 y =   22
    Size: w =   44 h =   33

3) Shape1 : TShape.Draw
Position: x =   11 y =   22
    Size: w =   44 h =   33

4) Shape2 : TShape.Draw
Position: x =    2 y =    2
    Size: w =    2 h =    2

5) Shape2 : TShape.Draw
Position: x =  111 y =  222
    Size: w =  444 h =  444

6) Shape2 : TShape.Draw
Position: x =  111 y =   88
    Size: w =  444 h =  444


TShape.CleanUp
TShape.CleanUp
TShape.CleanUp


オブジェクト - 続く

Programming Using Objects Page 2

外部リンク