DelphiBasics
  Home  |  Abstraction
 Documents
 Tutorials

 Writing your first program
 Writing your second program
 Amending this program

 Delphi data types
   Numbers
   Text (strings and chars)
   Sets and enumerations
   Arrays
   Records

 Programming logic
   Looping
   SubRoutines
   Exception handling

 Dates and times

 Files

 Pointers

 Printing text and graphics

 Object Orientation basics
   Memory leaks!
   Inheritance
   Abstraction
   Interfaces
   An example class

 References

 Standard components

 Articles

 A brief history of Delphi

 Usability : file handling

 Usability : reference books

 Author links

  Abstraction
Why abstract?
Whether in Delphi, or in the real World, classes are groupings of objects. Cars are a good example. They are all different, but have a common characteristic that allows you and I to call them by the same generic name of car. You drive, park and service your car in much the same way as everyone else. Except that your car has it's own particular way of doing these things.
 
So you can say that the class of cars has a common method for driving, parking and so on, but that this method can only be provided as a concept at the generic car level. It can only be properly described for each particular car model. The car class is the best place to put a placeholder for these methods, to ensure that each car can do these things, and in a relatively similar way. This placeholder in the Car class is empty - it simply paves the way for sub-classes to provide a real method. In Delphi terms, it is called an abstract method. It is an abstraction of the method, forcing the child classes (the car models) to flesh out the details of the method for itself.
 

An example
For our example, we will keep it simple. We'll look at the class of polygons, such as triangles, squares, pentagons, and so on. It's a neat and simple classification - these shapes look pretty much the same, tending to circular as the number of sides increases. There is a lot of commonality that we might want all sub-classes (triangle, square and so on) to have:
 
 Give the area inside the polygon
 Give the number of sides it has
 Give the length of each side

The parent class does not know how to implement these methods because it does not know the number of sides. This is why they are abstract. The triangle class, for example, extends this parent polygon class, and implements the above methods. For simplicity, we will use a fixed length for each side. So this goes inside the parent polygon class. And the parent can provide its own, non-abstract propetry for getting and setting this value.
 
This shows that a class can have both abstract and non-abstract (concrete) methods.
 
In summary, we have a polygon class with one concrete method, and two abstract methods. A triangle class, for example, would inherit the concrete side length get method, and must implement the abstract methods (unless it wants to be an abstract class itself).
 
The polygon master class
Let us define the polygon class first:
 
 type
  // Define our polygon base class
   TPolygon = class
   private
     sideLength : Byte;
     sideCount  : Byte;
 
    // Define concrete methods - implemnted in this parent class
     function  GetSideLength : Byte;
     procedure SetSideLength(length : Byte);
 
    // Define abstract methods - they must be implemented in a sub-class
     function  GetSideCount : Byte;        virtual; abstract;
     procedure SetSideCount(count : Byte); virtual; abstract;
     function  GetArea : Single;           virtual; abstract;
 
   published
    // Properties used to access private data fields
    // They use private methods to do this
     property length : Byte
         read GetSideLength
        write SetSideLength;
 
     property count : Byte
         read GetSideCount
        write SetSideCount;
 
    // The polygon constructor
     constructor Create(length : Byte);  virtual;
   end;

Notice that the abstract methods must also be defined as virtual (or dynamic. The dynamic construct is semantically equivalent to virtual. Dynamic optimises for code size, virtual for speed). This is allows us to override them in sub-classes.
 
To accompany this class definition, we must define the concrete methods defined for this, along with the constructor:
 
 // Implement a concrete method of TPolygon
 function TPolygon.GetSideLength : Byte;
 begin
   Result := sideLength;   // Return the side length as crrently set
 end;
 
 // Implement a concrete method of TPolygon
 procedure TPolygon.SetSideLength(length : Byte);
 begin
   sideLength := length;   // Set the side length from the passed parameter
 end;
 
 // Implement the TPolygon constructor
 constructor TPolygon.Create(length : Byte);
 begin
  // Save the passed parameter
   sideLength := length;
 end;

Defining a triangle sub-class
Here we define our first sub-class of TPolygon:
 
 // Define our triangle sub-class
 type
   TTriangle = class(TPolygon)
   private
     function  GetSideCount : Byte;        override;
     procedure SetSideCount(count : Byte); override;
     function  GetArea : Single;           override;
   published
     constructor Create(length : Byte);    override;
   end;

There are a number of things to note here. First, that we have specified TPolygon as our base class. Second, that we have defined methods to override the abstract classes in the master class. This helps to clarify the fact that we are not defining them here for the first time. Third, that we do not have to redeclare concrete methods and the properties of TPolygon. We inherit them.
 
We even override the constructor - so that we can se the side count of each object to suit the object shape. Such as 3 for a triangle.
 
We must implement these override methods:
 
 // Implement the triangle private methods
 function TTriangle.GetSideCount : Byte;
 begin
   Result := sideCount;
 end;
 
 procedure TTriangle.SetSideCount(count : Byte);
 begin
   sideCount := count;   // Set the side count from the passed parameter
 end;
 
 function TTriangle.GetArea : Single;
 begin
  // Return the area of the triangle
   Result := sideLength * sideLength * SQRT(3)/4;
 end;
 
 constructor TTriangle.Create(length : Byte);
 begin
  // Save the passed parameter
   sideLength := length;
 
  // And set the side count to 3 - a triangle
   sideCount := 3;
 end;

Defining a square subclass
We can define a square subclass similarly. We'll see this subclass in the full code below. It is similar to the triangle class.
 
Showing the full code
Now let us show these three classes altogether in one full unit that you can copy and paste into Delphi, remembering to follow the instructions at the start.
 
 // Full Unit code.
 // -----------------------------------------------------------
 // You must store this code in a unit called Unit1 with a form
 // called Form1 that has an OnCreate event called FormCreate.
 
 unit Unit1;
 
 interface
 
 uses
   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Dialogs;
 
 type
  // Define our polygon base class
   TPolygon = class
   private
     sideLength : Byte;
     sideCount  : Byte;
 
    // Define concrete methods - implemnted in this parent class
     function  GetSideLength : Byte;
     procedure SetSideLength(length : Byte);
 
    // Define abstract methods - they must be implemented in a sub-class
     function  GetSideCount : Byte;        virtual; abstract;
     procedure SetSideCount(count : Byte); virtual; abstract;
     function  GetArea : Single;           virtual; abstract;
 
   published
    // Properties used to access private data fields
    // They use private methods to do this
     property length : Byte
         read GetSideLength
        write SetSideLength;
 
     property count : Byte
         read GetSideCount
        write SetSideCount;
 
    // The polygon constructor
     constructor Create(length : Byte);    virtual;
   end;
 
  // Define our triangle sub-class
   TTriangle = class(TPolygon)
   private
     function  GetSideCount : Byte;        override;
     procedure SetSideCount(count : Byte); override;
     function  GetArea : Single;           override;
   published
     constructor Create(length : Byte);    override;
   end;
 
  // Define our triangle sub-class
   TSquare = class(TPolygon)
   private
     function  GetSideCount : Byte;        override;
     procedure SetSideCount(count : Byte); override;
     function  GetArea : Single;           override;
   published
     constructor Create(length : Byte);    override;
   end;
 
  // The form class itself
   TForm1 = class(TForm)
     procedure FormCreate(Sender: TObject);
   end;
 
 var
   Form1: TForm1;
 
 implementation
 
 {$R *.dfm}
 
 // Implement a concrete method of TPolygon
 function TPolygon.GetSideLength : Byte;
 begin
   Result := sideLength;   // Return the side length as crrently set
 end;
 
 // Implement a concrete method of TPolygon
 procedure TPolygon.SetSideLength(length : Byte);
 begin
   sideLength := length;   // Set the side length from the passed parameter
 end;
 
 // Implement the TPolygon constructor
 constructor TPolygon.Create(length : Byte);
 begin
  // Save the passed parameter
   sideLength := length;
 end;
 
 // Implement the triangle private methods
 function TTriangle.GetSideCount : Byte;
 begin
   Result := sideCount;
 end;
 
 procedure TTriangle.SetSideCount(count : Byte);
 begin
   sideCount := count;   // Set the side count from the passed parameter
 end;
 
 function TTriangle.GetArea : Single;
 begin
  // Return the area of the triangle
   Result := sideLength * sideLength / 2;
 end;
 
 constructor TTriangle.Create(length : Byte);
 begin
  // Save the passed parameter
   sideLength := length;
 
  // And set the side count to 3 - a triangle
   sideCount := 3;
 end;
 
 // Implement the square private methods
 function TSquare.GetSideCount : Byte;
 begin
   Result := sideCount;
 end;
 
 procedure TSquare.SetSideCount(count : Byte);
 begin
   sideCount := count;   // Set the side count from the passed parameter
 end;
 
 function TSquare.GetArea : Single;
 begin
  // Return the area of the triangle
   Result := sideLength * sideLength;
 end;
 
 constructor TSquare.Create(length : Byte);
 begin
  // Save the passed parameter
   sideLength := length;
 
  // And set the side count to 4 - a square
   sideCount := 4;
 end;
 
 // Main line code
 procedure TForm1.FormCreate(Sender: TObject);
 var
   triangle : TTriangle;
   square   : TSquare;
 begin
  // Create triangle and square objects
   triangle := TTriangle.Create(10);
   square   := TSquare.Create(10);
 
  // Show the area of each polygon
   ShowMessageFmt('Triangle with side length %d area = %f',
                  [triangle.length, triangle.GetArea]);
 
   ShowMessageFmt('Square with side length %d area = %f',
                  [square.length, square.GetArea]);
 
  // Now change the side length of the triangle and reshow
   triangle.length := 5;
   ShowMessageFmt('Triangle with side length %d area = %f',
                  [triangle.length, triangle.GetArea]);
 end;
 end.

 The ShowMessage rountines display the following :
 
 Triangle with side length 10 area = 50.0
 Square with side length 10 area = 100.0
 Triangle with side length 5 area = 12.5

Note that we have used triangle and square routines to get the area, and a polygon defined routine to get and set the side length.
 

A tangible benefit of abstraction
If the above has appeared a little academic, then there is one nice benefit of this abstraction. Whilst an abstract class such as a polygon cannot be used to create a new object (such an object would not be fully defined), you can create a reference to one. By doing this, you can point this reference to any sub-class, such as a triangle, and use the polygon class abstract methods. Your code can process arrays of sub-classes, and treat them all as if they were generic polygons, without needing to know what variants they each are.
 

How do abstract classes differ from Interfaces?
Abstract classes and Interfaces are quite similar, in that they both provide placeholder methods. Their approach is quite different though. Interfaces are not classes. Your class does not extend an interface. The nature of your class has basically nothing to do with the interface. It can be a new class, or it can extend an existing class. The placeholder methods in the Interface are implemented in your class. All of them must be implemented, in addition to other methods in your class. By implementing the interface, your class simply adds a standard set of metods or properties - a flavour in common with other classes implementing the interface.
 
Note also that a class may extend only one ancestor class, but can implement multiple interfaces.
 
 
 

Delphi Basics © Neil Moffatt All rights reserved.  |  Home Page