更具体地说,这是一个视觉对象列表,用于绘制画布.我有一个公共类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列表对象传递给其构造函数中的每个单独项目,并让每个项目在项目列表中注册其自身.在这种情况下,项目列表将没有任何添加过程,我可能甚至不需要单独的列表对象.我确信应该有一些重要的技巧,我错过了,我愿意对结构进行任何大规模的更改,以便更有效地适应这种情况.
这是 polymorphism的经典用法.根据 XE2 documentation(C,但适用于此处):Polymorphic classes: Classes that provide an identical interface, but can be implemented to serve different specific requirements, are referred to as polymorphic classes. A class is polymorphic if it declares or inherits at least one virtual (or pure virtual) function.
这是一个完全符合您想要做的事情的例子.它创建了一个基类型(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.
控制台窗口的输出如下所示: