Difference between revisions of "Programming with Objects and Classes"

From Lazarus wiki
Jump to navigationJump to search
(Redirect to newer page name)
 
(15 intermediate revisions by 4 users not shown)
Line 1: Line 1:
== Overview ==
+
#REDIRECT [[Object Oriented Programming with Free Pascal and Lazarus#Free Pascal Language Extensions]]
 
 
FPC includes several language extensions to its "standard" Pascal syntax to support object oriented programming. 
 
 
 
* Objects    (chapter 5)
 
* Classes    (chapter 6)
 
* Interfaces (chapter 7)
 
* Generics  (chapter 8)
 
 
 
These extensions are described in the indicated chapters of the FPC Language Reference Guide: http://www.freepascal.org/docs.var. The Language Reference Guide includes syntax diagrams and further details not contained in this introductory tutorial.  Of the four language features listed above, '''Objects''' and '''Classes''' form the basis of object oriented programming (OOP) in FPC and Lazarus.  For those new to OOP, the Objects section includes more introductory concepts and the Classes section minimizes repetition by emphasizing the similarities and differences to the Objects syntax.
 
 
 
Users migrating from Delphi may want to skip the section on Objects and go to the Classes section since the Classes version of FPC OOP is based on Delphi syntax.  Conversely, users familiar with the older Turbo Pascal OOP implementation may initially want to skip the section on Classes since the Objects implementation is based on the older Turbo Pascal dialect.  For Mac developers familiar with the various Apple based Object Pascal dialects, neither the FPC Objects or Classes implementations provide a direct migration path.  In general, the '''Classes''' implementation seems to be more widely in use including Delphi ports and also Lazarus development.  Currently, as of March 2009, there are discussions on the Mac Pascal Mailing list to potentially include some compiler support (new syntax) to  provide access to Apple's Objective C / Cocoa framework.
 
 
 
== General Concepts ==
 
 
 
OOP provides different ways to manage and encapsulate data and to manage program flow compared with other available programming language features.  This method of programming often lends itself to modeling certain applications such as Graphic User Interfaces and physical systems in a more facile manner.  However it is not appropriate for all applications.  Program control is not as explicit as using the more standard Pascal procedural constructs and to get the most benefit of OOP, understanding of large class libraries is often required.  Also, maintaining large OOP application code has its advantages and disadvantages compared to maintaining procedural code.    There are many sources for learning OOP and OO design which are beyond the scope of this guide. 
 
 
 
There are a lot of programming languages which incorporate OOP features as extensions or the basis of their language.  As such, there are many different terms for describing OO concepts.  Even within FPC, some of the terminology overlaps.  In general, OOP usually consists of the concept of a programming object (or information unit) which explicitly combines and encapsulates a set of data and procedural code which are related.  Usually, this data is persistent during program execution without having to be declared globally.  In addition, there are usually features which enable additional objects to be incrementally modified and/or extended based on previously defined objects which is usually referred to by the term "inheritance."    Many languages refer to the procedures which belong to an object as an object "method."  Much of the power of OOP is realized by late dynamic binding of methods at run time rather than at compile time.  This dynamic binding of methods is similar to using procedural variables and procedural parameters but with greater syntactic cohesion with the data it is related to.
 
 
 
== Objects - Basics ==
 
 
 
Of the two OOP implementations FPC provides, the one used less often seems to be what is referred to as "Objects" which probably gets its name from the type definition syntax.  The syntax diagram for an object type declaration can be found in the FPC language reference - Chapter 5.  Basically, an '''object''' type looks like a '''record''' type with additional fields including procedure fields and also optional keywords which indicate the scope of the fields.  In fact, an oversimplified (but valid) simple object is hard to distinguish from a record structure as can be seen below.
 
 
 
<delphi>
 
Type
 
  MyObject = Object
 
      f_Integer : integer;
 
      f_String : ansiString;
 
      f_Array : array [1.3] of char;
 
  end;
 
</delphi>
 
 
 
The only difference in the above example is that the Pascal keyword '''record''' has been replaced with the keyword '''object'''.  Things start to be different when methods are added to the object.  Object methods are declared in FPC using the keywords '''procedure''' or '''function'''.  These object methods (procedures or functions) are declared the same way as normal Pascal procedures and functions; just that they are declared within the object declaration itself.  Lets create a different object; this time possibly as part of an oversimplified graphics drawing program.
 
 
 
<delphi>
 
Type
 
  DrawingObject = Object
 
      x, y : single;
 
      height, width : single;
 
      procedure Draw;
 
  end;
 
 
 
Var
 
  Rectangle : DrawingObject;
 
</delphi>
 
 
 
Besides the simple datatype fields providing some application specific location and size attributes, the object shown above declares an additional parameter;  a procedure called ''Draw.'' The type declaration is followed by a variable identifier called ''Rectangle'' of the type ''DrawingObject''.  Next, the ''Draw'' procedural code itself needs to be written as well as well as code for accessing and manipulating the data fields, as well as how to invoke the Draw procedure.  The following simple program shows how all this works.  It should compile and run on any system with FPC 2.2.2 and above.  Note: For Mac OS X, the -macpas compiler directive must be turned off.
 
 
 
<delphi>
 
Program TestObjects;
 
 
 
Type
 
  DrawingObject = Object
 
      x, y : single;
 
      height, width : single;
 
      procedure Draw;  //  procedure declared in here
 
  end;
 
 
 
  procedure DrawingObject .Draw;
 
  begin
 
      writeln('Drawing an Object');
 
      writeln(' x = ', x, ' y = ', y);  // object fields
 
      writeln(' width = ', width);
 
      writeln(' height = ', height);
 
      writeln;
 
//    moveto (x, y);  // probably would need to include a platform dependent drawing unit to do actual drawing
 
//    ... more code to actually draw a shape on the screen using the other parameters
 
  end;
 
 
 
Var
 
  Rectangle : DrawingObject;
 
 
 
begin
 
 
 
  Rectangle.x:= 50;  //  the fields specific to the variable "Rectangle"
 
  Rectangle.y:= 100;
 
  Rectangle.width:= 60;
 
  Rectangle.height:= 40;
 
 
 
  writeln('x = ', Rectangle.x);
 
 
 
  Rectangle.Draw;  //  Calling the method (procedure)
 
 
 
  with Rectangle do  //  With works the same way even with the method (procedure) field
 
  begin
 
      x:= 75;
 
      Draw;
 
  end;
 
 
 
end.
 
</delphi>
 
 
 
As can be seen in the above program, the body of the ''Draw'' method (procedure) is declared after the object type declaration by concatenating the type identifier with the procedure name.  In a more realistic situation, the object would likely be declared in the '''interface''' section of a separate unit while the procedure body would be written in the '''implementation''' section of the same external unit.  In this example, only standard vanilla Pascal is used, but if actual graphic primitives are available, they can be used if desired.  The second thing to notice is that inside the ''Draw'' method (procedure), the object's data fields are referenced as if they were regular local variables.  The only difference to regular local variables is that the values of these fields will persist between calls to the ''Draw'' procedure.
 
 
 
In the main program, the fields are assigned values and accessed just like record fields are.  Similarly, the ''Draw'' procedure is invoked using the same dot notation as the fields.  And like records, the '''with''' keyword works the same for accessing object fields and invoking methods.  Finally, notice that the second time the ''Draw'' method is called, all the fields persisted between calls; the only field different is the x attribute which was explicitly changed.
 
 
 
The above example is meant to show the basic mechanics of simple '''Objects'''.  There are a couple of problems, however.  The first problem is that the ''Draw'' method only draws one thing and that is a rectangle (although even that is questionable.)  Additional methods could be declared in the '''DrawingObject''' object such as DrawRectangle, DrawCircle, DrawTriangle, etc..., but this would not be too much different than declaring separate regular procedures and having to use '''case''' statements to select the appropriate procedure.  So doing it this way with objects would require more work (and is not how it is accomplished.)  The other problem is, the object's fields are able to be accessed and modified anywhere in the program very similar to using global variables which makes it harder to write well encapsulated and robust programs.  These problems will be addressed in the next section.
 
 
 
== Objects - Static Inheritance ==
 
 
 
The next step will expand upon the previous code handle rectangles, squares, and triangles.  An attempt will be made to leverage the existing code, if possible, by creating two new objects to handle the two new shapes.  In order to make the code and output easier to follow,  the main object type, ''DrawingObject'' has been renamed to ''TShape''.  Other object types will be named ''TRectangle'', ''TSquare'', and ''TTriangle'' with corresponding variables named Shape, Rectangle, Square and Triangle.  The letter "T" is used as a prefix to the object type names since it is a commonly used convention in many object libraries and frameworks.
 
 
 
What follows are type declarations for: the main ''TShape'' object type (previously ''DrawingObject'') along with the new types ''TRectangle'' and ''TSquare''.  ''TShape'' now includes a new method ('''procedure''' GetParams) to obtain values for its fields.  Next, the types ''TRectangle'' and ''TSquare'' are declared.  ''TShape'' is usually referred to as an ancestor or parent object, while TRectangle is said to be a child or subobject or subclass of ''TShape'' or that it descends from ''TShape''.  This parent child, grandchild heiracry is identified by the qualifier type name following the Object keyword.  Similarly, TSquare is a child of TRectangle. 
 
 
 
<delphi>
 
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;
 
</delphi>
 
 
 
Notice that ''TRectangle'' lists only  the ''Draw'' procedure while ''TSquare'' lists both the ''GetParams'' and ''Draw'' procedures.  Neither of the subobject types show any fields.  The "missing" fields and procedure names are said to be inherited from the fields and procedures declared in the ancestor objects.  In this case, any object variables declared and instantiated of type ''TRectangle'' will inherit all four fields, (x, y, height, and width) and the ''GetParams'' method from the ''TShape'' type.  At runtine, a variable of the type ''TRectangle''  will look and behave like a ''TShape'' variable except that the ''Draw'' procedure will use a different code block than the procedure by the same name in an instantiated  variable of the ''TShape'' type.  Similarly, the ''TSquare'' object type will inherit all the "grandparent" fields from ''TShape'' and have its own different procedure blocks.  If desired, ''TRectangle'' and ''TSquare'' could have declared additional fields in their type definitions which would show up as additional fields (memory locations) available at runtime which instantiated parent object variables would not have.
 
 
 
Next are the specific procedure implementations for these object types.
 
 
 
<delphi>
 
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;
 
</delphi>
 
 
 
To provide clarity, no actual (platform specific) drawing routines are used.  Rather, generic Pascal I/O routines are included to illustrate the runtime behavior of objects.  This particular hierarchy of objects, fields and methods would likely not be the best implementation for an actual shape drawing application and this will become apparent after new concepts are introduced.   
 
 
 
The ''GetParams'' method is used to obtain and to perform any needed processing of the field values.  Although the object fields could accessed directly as was done in the previous section, it is considered better programming style to encapsulate the "getting" a "setting" of object fields using object methods.  In fact, FPC provides additional language features to help support and enforce narrowing the accessibility and visibility of internal object data.
 
 
 
Since the different types of objects in this program all use the same fields, using a common ''GetParams'' method would seem to make sense to include at the top level object which all other descendent objects can inherit.  Thus, this method is implemented in the TShape object type.
 
 
 
The ''TShape'' object type includes a ''Draw'' method which is probably not needed in an actual shape drawing program.  However, a variable declared of type ''TShape'' likely will be assigned to a variable of a descendent object type such as ''TRectangle'' and as such needs to have a draw method which will draw using the code of the descendent object type.  Normally, this method would just be declared as a stub or not implemented at all.  The syntax for this type of situation and others will be described in a later section.  In this case, we want to provide some feedback for field values and program flow so it includes some writeln statements.
 
 
 
The ''TRectangle'' object type does not include a ''GetParams'' method but does have its own ''Draw'' method.  The ''TSquare'' object type has it's own ''GetParams'' method which repeats much of the the same code from the ''TShape.GetParams'' method but also includes code to ensure that the height and width fields are the same.  It then defines its own ''Draw'' method.  In an actual program, the ''Draw'' method for squares and rectangles would probably be the same and the ''Draw'' method could be omitted for TSquare.
 
 
 
Here is a sample program which demonstrates the behavior of the various objects.
 
 
 
<delphi>
 
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;
 
</delphi>
 
 
 
The above code produces the following output.
 
 
 
<pre>
 
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
 
</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 T-square type (as would be reasonable for an actual program) the last output line would look like this.
 
 
 
<pre>
 
Calling Square.Draw : TRectangle.Draw
 
</pre>
 
 
 
Now what happens if we assign a sub object to a parent object as follows?
 
 
 
<delphi>
 
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;
 
</delphi>
 
<pre>
 
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
 
</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 are what is printed out.  This behavior is called Static method inheritance in FPC.  If it is desired to instead invoke the TRectangle.Draw method in this situation, FPC provides way to do this 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.
 
 
 
Assigning an ancestor object to a descendent object is not allowed.  The compiler will flag the following line as an error.
 
 
 
<delphi>
 
Rectangle := Shape;  <--- can't assign a parent to a child, does not compile
 
</delphi>
 
 
 
== Objects - Dynamic Inheritance ==
 
 
 
For a drawing application, a common task would be to refresh the display and stepping through an array or  linked list of shapes and calling the draw method.  Instead of needing a case statement inside the loop which selects one of many possible specific drawing procedures
 
 
 
<delphi>
 
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
 
</delphi>
 
the code would look something like this
 
<delphi>
 
for k:= 1 to Numshapes do
 
Shape[k].draw;
 
</delphi>
 
 
 
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:
 
 
 
<delphi>
 
Type
 
  TShape = Object
 
      x, y : single;
 
      height, width : single;
 
      procedure GetParams;
 
      procedure Draw; virtual;
 
  end;
 
 
TRectangle = Object(TShape)
 
      procedure Draw; virtual;
 
  end;
 
</delphi>
 
 
 
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.
 
 
 
<pre>
 
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
 
</pre>
 
 
 
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.
 
 
 
== Objects - Constructors and Destructors ==
 
 
 
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.
 
 
 
<delphi>
 
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;
 
</delphi>
 
 
 
The TShape object type includes fields, two virtual methods (Draw and GetParams), a constructor called Init and a destructor called CleunUp. 
 
 
 
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.
 
 
 
<delphi>
 
  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;
 
</delphi>
 
 
 
Again, the implentation of constructors look like regular methods except using the keywords '''constructor''' and '''destructor''' instead of '''procedure''' and '''function'''.  Consider the following main program.
 
 
 
<delphi>
 
  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;
 
end.
 
</delphi>
 
 
 
which produces the following output.
 
 
 
<pre>
 
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
 
</pre>
 
 
 
== Classes ==
 
 
 
Waiting for the construction contract
 

Latest revision as of 05:11, 8 July 2016