我正在构建一个自定义OpenGL控件,它包含一个项目列表,其中每个项目可能是不同的类,但继承自某个公共类类型。我不知道如何以这种方式执行此操作,我可以循环执行这些项目并执行某些预期在继承的类中被覆盖的操作。
更具体地说,这是一个可视对象列表,它们可以绘制到画布上。我有一个公共类TGLItem
,用于创建许多继承类。例如,TGLCar
继承自TGLItem
,并已添加到列表TGLItems
中。此自定义列表类是控件的一部分。
当控件绘制时,它会遍历此项目列表并调用每个项目的Draw
过程。 Draw
旨在被实际绘制项目的继承类覆盖。所以真正的工作是从Draw
类中实现的TGLCar
过程完成的,但只能从主控件中调用。
主控件(TGLImage
)不知道实际继承的项目是什么,但能够调用其Draw
过程,期望它绘制到OpenGL。
如何以适应此方案的方式构建此项目列表和项目库?这是我到目前为止所做的:
TGLItems = class(TPersistent)
private
FItems: TList;
function GetItem(Index: Integer): TGLItem;
procedure SetItem(Index: Integer; const Value: TGLItem);
public
constructor Create;
destructor Destroy; override;
procedure Add(AItem: TGLItem);
function Count: Integer;
property Items[Index: Integer]: TGLItem read GetItem write SetItem; default;
end;
TGLItem = class(TPersistent)
private
FPosition: TGLPosition;
FDimensions: TGLDimensions;
FOwner: TGLItems;
FItemClass: TGLItemClass;
procedure PositionChanged(Sender: TObject);
procedure DimensionsChanged(Sender: TObject);
procedure SetPosition(const Value: TGLPosition);
procedure SetDimensions(const Value: TGLDimensions);
public
constructor Create(Owner: TGLItems);
destructor Destroy; override;
procedure Draw;
property Owner: TGLItems read FOwner;
property ItemClass: TGLItemClass read FItemClass;
published
property Position: TGLPosition read FPosition write SetPosition;
property Dimensions: TGLDimensions read FDimensions write SetDimensions;
end;
...实施
{ TGLItem }
constructor TGLItem.Create;
begin
FPosition:= TGLPosition.Create;
FPosition.OnChange:= PositionChanged;
FDimensions:= TGLDimensions.Create;
FDimensions.OnChange:= DimensionsChanged;
end;
destructor TGLItem.Destroy;
begin
FPosition.Free;
FDimensions.Free;
inherited;
end;
procedure TGLItem.DimensionsChanged(Sender: TObject);
begin
end;
procedure TGLItem.Draw;
begin
//Draw to gl scene
end;
procedure TGLItem.PositionChanged(Sender: TObject);
begin
end;
procedure TGLItem.SetDimensions(const Value: TGLDimensions);
begin
FDimensions.Assign(Value);
end;
procedure TGLItem.SetPosition(const Value: TGLPosition);
begin
FPosition.Assign(Value);
end;
{ TGLItems }
procedure TGLItems.Add(AItem: TGLItem);
begin
FItems.Add(AItem);
//Expects objects to be created and maintained elsewhere
//This list object will not create/destroy any items
end;
function TGLItems.Count: Integer;
begin
Result:= FItems.Count;
end;
constructor TGLItems.Create;
begin
FItems:= TList.Create;
end;
destructor TGLItems.Destroy;
begin
FItems.Free;
inherited;
end;
function TGLItems.GetItem(Index: Integer): TGLItem;
begin
Result:= TGLItem(FItems[Index]);
end;
procedure TGLItems.SetItem(Index: Integer; const Value: TGLItem);
begin
TGLItem(FItems[Index]).Assign(Value);
end;
OpenGL的一部分并不一定与这种情况有关,我只是想解释一下这个目的的一些细节,以便了解我希望如何发挥作用。
我还想到将TGLItems
列表对象传递给其构造函数中的每个单独项目,并让每个项目在项目列表中注册其自身。在这种情况下,项目列表将没有任何添加过程,我可能甚至不需要单独的列表对象。我确信应该有一些我失踪的大技巧,并且我愿意对结构进行任何大规模的更改,以便更有效地适应这种情况。
答案 0 :(得分:5)
这是polymorphism的经典用法。根据{{3}}(C ++,但在此适用):
多态类:提供相同接口但可以实现以满足不同特定要求的类称为多态类。如果一个类声明或继承至少一个虚拟(或纯虚拟)函数,则该类是多态的。
这是一个完全符合您想要做的事情的例子。它创建一个基类型(TBase
),其中包含每个后代必须实现的抽象虚方法(Draw
),以及两个独立的后代类型(TChildOne
和TChildTwo
),每个类型其中实现了它自己的Draw
方法。
声明了一个TBase
数组,包含10个项目(请参阅NumChildren
常量)和SetLength(BaseArray, NumChildren)
行。迭代数组,如果当前索引是奇数,则创建一个子类型实例;如果它是偶数,则创建另一个子类型。
然后再次反向迭代数组,并调用泛型TBase.Draw
。代码根据正在调用的类类型Draw
输出不同的行前缀。请注意,对每个数组项Draw
的调用只是调用TBase.Draw
(不检查该索引处数组中的类型),但是不同的特定Draw
方法正在调用类型,具体取决于在该索引处的数组中找到的类型。
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
SysUtils; // XE2: uses System.SysUtils;
type
TBase = class(TObject)
procedure Draw(const Msg: string); virtual; abstract;
end;
TChildOne = class(TBase)
procedure Draw(const Msg: string); override;
end;
TChildTwo = class(TBase)
procedure Draw(const Msg: string); override;
end;
TBaseArray = array of TBase;
procedure TChildOne.Draw(const Msg: string);
begin
// Hard-coded for clarity. Change to something like this
// to see without hard-coded name
// WriteLn(Self.ClassName + '.Draw: ', Msg);
Writeln('Type TChildOne.Draw: ', Msg);
end;
procedure TChildTwo.Draw(const Msg: string);
begin
// Note missing 'T' before class type to make more apparent.
// See note in TChildOne.Draw about removing hard-coded classname
WriteLn('Type ChildTwo.Draw: ', Msg);
end;
var
BaseArray: TBaseArray;
i: Integer;
const
NumChildren = 10;
begin
SetLength(BaseArray, NumChildren);
for i := 0 to NumChildren - 1 do
begin
if Odd(i) then
BaseArray[i] := TChildOne.Create
else
BaseArray[i] := TChildTwo.Create;
end;
for i := NumChildren - 1 downto 0 do
BaseArray[i].Draw('This is index ' + IntToStr(i));
Readln;
end.
控制台窗口的输出如下所示: