Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Inherit Me This... Inherit Me That....

0.00/5 (No votes)
26 Nov 2011 1  
This article explains how inheritance in Delphi helps you to design and create your classes using a nice Object Orientated approach

Introduction

This article is my humble attempt at explaining how Inheritance and Polymorphism can be used to help you use an object orientated guideline for creating classes and creating descendants of these classes and a cool trick for creating the correct instance of the descendant class you want to create.

Background

Whether you know it or not, we use inheritance every time we use Delphi. When you create a new form, you are in fact using inheritance.

TForm1 = class(TForm)
end;  

TForm1 is inheriting from the base class TForm. This is how your new form knows exactly how to be a form (like how it must be drawn, what events it has, what it can do, etc.) without you needing to do anything extra. But how can you use this to make your applications more robust and leverage this kind of practice? Well, this is what I am hopefully going to help demystify.

What the Code Does

All the demonstration application does is draw two types of shapes in 4 different colours. I start by making a (base) class, TShape, and create two descendant classes TEllipse and TRectangle. The aim of TShape is to draw a shape onto a TPaintBox component. When the TShape class is defined, we know the following information:

  • What color I need to draw with
  • The co-ordinates where that needs to be drawn

So the class definition looks like this:

  TShape = class(TObject)
  private
    FTop : Integer;
    FBottom : Integer;
    FRight : Integer;
    FLeft : Integer;
    FBrushColor : TColor;

    procedure SetTopLeft(const Value : TPoint);
    function GetBottomRight : TPoint;
    function GetTopLeft : TPoint;
    procedure SetBottomRight(const Value : TPoint);
  public
    constructor Create(
      const ABrushColor : TColor;
      const ATop : Integer;
      const ALeft : Integer); overload;
    constructor Create(
      const ABrushColor : TColor;
      const ATop : Integer;
      const ALeft : Integer;
      const ARight : Integer;
      const ABottom : Integer); overload;
    constructor Create(
      const ABrushColor : TColor;
      ATopLeft : TPoint); overload;
    constructor Create(
      const ABrushColor : TColor;
      ATopLeft : TPoint;
      ABottomRight : TPoint); overload;

    procedure Draw(
      ACanvas : TCanvas); virtual; abstract;

    property Top : Integer read FTop write FTop;
    property Left : Integer read FLeft write FLeft;
    property Right : Integer read FRight write FRight;
    property Bottom : Integer read FBottom write FBottom;
    property TopLeft : TPoint read GetTopLeft write SetTopLeft;
    property BottomRight : TPoint read GetBottomRight write SetBottomRight;
    property BrushColor : TColor read FBrushColor;
  end; 

The only thing the TShape object will not know is actually HOW to draw itself. This is where Polymorphism comes in. In the TEllipse and TRectangle, you will see an overridden method Draw. This is how the TShape will know how to actually do what it needs to do. And the definition for them looks like this:

  TEllipse = class(TShape)
  public
    procedure Draw(
      ACanvas : TCanvas); override;
  end;

// Actual implementation...
procedure TEllipse.Draw(
  ACanvas: TCanvas);
begin
  ACanvas.Brush.Color := BrushColor;
  ACanvas.Ellipse(TopLeft.X, TopLeft.Y, BottomRight.X, BottomRight.Y);
end;

  TRectangle = class(TShape)
  public
    procedure Draw(
      ACanvas : TCanvas); override;
  end;

// Actual implementation...
procedure TRectangle.Draw(
  ACanvas: TCanvas);
var
  LRect : TRect;
begin
  ACanvas.Brush.Color := BrushColor;
  LRect.TopLeft := TopLeft;
  LRect.BottomRight := BottomRight;
  ACanvas.Rectangle(LRect);
end;

And how that Draw method is implemented will determine how the shape is actually drawn. If you decide to build your own descendant from the TShape object, you will know what I mean :).

PLEASE NOTE: The code in the source files has a lot of commenting in it to help explain what is happening. Please read the code to get a better indication of why I did things the way I did...

Now, in the main form, I need to ensure a few things:

  • What colour I must use
  • What shape I must use
  • What are my co-ordinates so I know where to draw
  • How many objects do I actually need to draw

This is why I use specific events and variables on my main form:

  • 2 variables to know where the points of my shape need to be
  • The mouse up and down events (on the paint box) to ensure that I can set the points of the shape correctly
  • An object which will hold all the TShape objects I need
  • Ensure that any TShape objects that are created are destroyed correctly

As such, my main form looks something like this:

  TMainFrm = class(TForm)
    procedure PaintBoxMouseDown(
      Sender : TObject;
      Button : TMouseButton;
      Shift : TShiftState;
      X : Integer;
      Y : Integer);
    procedure PaintBoxMouseUp(
      Sender : TObject;
      Button : TMouseButton;
      Shift : TShiftState;
      X : Integer;
      Y : Integer);
    procedure PaintBoxPaint(
      Sender : TObject);
    procedure FormCreate(
      Sender : TObject);
    procedure FormDestroy(
      Sender : TObject);
  private
    FTopLeft : TPoint;
    FBottomRight : TPoint;
    FShapes : TObjectList;
  end; 

With the implementation looking like this:

procedure TMainFrm.PaintBoxMouseDown(
  Sender : TObject;
  Button : TMouseButton;
  Shift : TShiftState;
  X : Integer;
  Y : Integer);
begin
  FTopLeft.X := X;
  FTopLeft.Y := Y;
end;

procedure TMainFrm.PaintBoxMouseUp(
  Sender : TObject;
  Button : TMouseButton;
  Shift : TShiftState;
  X : Integer;
  Y : Integer);
var
  LColor : TColor;
  LShapeRef : Ref_Shape;
begin
  case ComboBox1.ItemIndex of
    0 : LShapeRef := TEllipse;
    1 : LShapeRef := TRectangle;
  else
    LShapeRef := TEllipse;
  end;
  case ComboBox2.ItemIndex of
    0 : LColor := clRed;
    1 : LColor := clGreen;
    2 : LColor := clBlue;
    3 : LColor := clBlack;
  else
    LColor := clRed;
  end;
  FBottomRight.X := X;
  FBottomRight.Y := Y;
  FShapes.Add(LShapeRef.Create(LColor, FTopLeft, FBottomRight));
  PaintBox.Repaint;
end;

procedure TMainFrm.PaintBoxPaint(
  Sender : TObject);
var
  LShape : TShape;
  i : Integer;
  LCanvas : TCanvas;
begin
  LCanvas := TPaintBox(Sender).Canvas;
  LCanvas.Brush.Color := clWhite;
  LCanvas.FillRect(TPaintBox(Sender).Canvas.ClipRect);
  for i := 0 to FShapes.Count - 1 do
  begin
    LShape := TShape(FShapes.Items[i]);
    LShape.Draw(LCanvas);
  end;
end;

procedure TMainFrm.FormCreate(
  Sender : TObject);
begin
  FShapes := TObjectList.Create(True);
end;

procedure TMainFrm.FormDestroy(
  Sender : TObject);
begin
  FreeAndNil(FShapes);
end;

Once again, for simplicity's sake, I left out the comments. To see how this all ties in, all you need to do is run the accompanying application in the download and you are good to go :).

Points of Interest

Inadvertently, you have now been introduced to the Template Design Pattern. It leverages the fact that the base class(es) you define do certain common things for you that your descendant classes use so that you don't need to re-code them. This enables you to have cleaner, more maintainable code (because instead of the common code being rewritten in several places, it is now only in one place). It also allows you to design your descendant classes to focus on what they were actually created for without worrying about the common methods/actions/properties because that has already been catered for in the ancestor class.

You will also see in my code that I have a really strange declaration:

  Ref_Shape = class of TShape; 

This is called a class reference declaration and what that means is that you can create an instance of a TShape descendant using a variable of type Ref_Shape. Which is why you will see the code the way you do in the PantBoxMouseUp event. Cool trick hey? :).

For a more detailed example of how to use the topics I raised in this article, please take a gander at The Configurator[^].

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here