65.9K
CodeProject 正在变化。 阅读更多。
Home

继承我这个... 继承我那个...

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (9投票s)

2011年11月25日

CPOL

4分钟阅读

viewsIcon

22987

downloadIcon

153

本文解释了 Delphi 中的继承如何帮助您使用良好的面向对象方法设计和创建类。

引言

本文是我尝试解释如何使用继承和多态性来帮助您使用面向对象的指导原则创建类,创建这些类的子类,以及创建一个很酷的技巧来创建您想要创建的子类的正确实例。

背景

无论您是否知道,我们每次使用Delphi时都在使用继承。 当您创建一个新窗体时,实际上是在使用继承。

TForm1 = class(TForm)
end;  

TForm1 继承自基类 TForm。 这就是您的新窗体确切地知道如何成为一个窗体(例如如何绘制,它有哪些事件,它可以做什么等等)的原因,而无需您做任何额外的事情。 但是,您如何使用它来使您的应用程序更健壮并利用这种实践? 好吧,这就是我希望能够帮助解开的谜团。

代码的作用

所有演示应用程序所做的就是在4种不同的颜色中绘制两种类型的形状。 我首先创建一个(基)类 TShape,并创建两个子类 TEllipseTRectangleTShape 的目的是在 TPaintBox 组件上绘制一个形状。 当定义了 TShape 类时,我们知道以下信息

  • 我需要用什么颜色绘制
  • 需要在哪里绘制的坐标

因此,类定义如下所示

  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; 

TShape 对象唯一不知道的是实际如何绘制自己。 这就是多态性的用武之地。 在 TEllipseTRectangle 中,您将看到一个被重写的方法 Draw。 这就是 TShape 将知道如何实际执行它需要做的事情的方式。 它们的定义如下所示

  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;

以及 Draw 方法的实现方式将决定形状的实际绘制方式。 如果您决定从 TShape 对象构建自己的子类,您就会明白我的意思 :).

请注意:源文件中的代码有很多注释,以帮助解释正在发生的事情。 请阅读代码以更好地了解我为什么以这种方式做事...

现在,在主窗体中,我需要确保一些事情

  • 我必须使用什么颜色
  • 我必须使用什么形状
  • 我的坐标是什么,以便我知道在哪里绘制
  • 我实际上需要绘制多少个对象

这就是为什么我在主窗体上使用特定的事件和变量

  • 2 个变量用于知道我的形状的点需要在哪里
  • 鼠标抬起和按下事件(在绘制框上)以确保我可以正确设置形状的点
  • 一个对象,它将保存我需要的所有 TShape 对象
  • 确保正确销毁创建的任何 TShape 对象

因此,我的主窗体看起来像这样

  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; 

实现如下所示

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;

再次,为了简单起见,我省略了注释。 要了解这一切是如何联系起来的,您所需要做的就是在下载中运行随附的应用程序,您就可以开始了:)。

关注点

无意中,您现在被介绍了模板设计模式。 它利用了这样一个事实,即您定义的基类为您执行某些常见的操作,您的子类可以使用这些操作,因此您无需重新编码它们。 这使您可以拥有更清晰,更易于维护的代码(因为现在通用代码只在一个地方,而不是在多个地方重写)。 它还允许您设计子类以专注于实际创建的目的,而不必担心通用方法/操作/属性,因为这些已经在父类中得到了满足。

您还将在我的代码中看到一个非常奇怪的声明

  Ref_Shape = class of TShape; 

这被称为类引用声明,这意味着您可以使用 Ref_Shape 类型的变量来创建 TShape 子类的实例。 这就是为什么您会在 PantBoxMouseUp 事件中看到代码的编写方式。 酷技巧吧? :).

有关如何使用我在本文中提出的主题的更详细示例,请浏览 The Configurator[^]。

© . All rights reserved.