|
| 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 / 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;
|
|
| 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. | |
| | | | |