5

各アイテムが異なるクラスである可能性があるが、特定の共通クラスタイプから継承されているアイテムのリストで構成されるカスタムOpenGLコントロールを構築しています。これらのアイテムをループして、継承されたクラスでオーバーライドされることが予想される特定のアクションを実行できる方法でこれを行う方法がわかりません。

具体的には、キャンバスに描画することを目的としたビジュアルオブジェクトのリストです。TGLItemいくつかの継承クラスを作成するために使用される共通クラスがあります。たとえば、TGLCarはから継承TGLItemされ、リストに追加されますTGLItems。このカスタムリストクラスは、コントロールの一部です。

コントロールが描画すると、このアイテムリストをループしてDraw、各アイテムのプロシージャを呼び出します。Drawアイテムの実際の描画が行われる継承クラスによってオーバーライドされることを目的としています。したがって、実際の作業はクラスDrawに実装されているプロシージャから実行されますTGLCarが、メインコントロールからのみ呼び出されます。

メインコントロール(TGLImage)は、実際に継承されたアイテムが何であるかを認識していませんが、DrawOpenGLに描画されることを期待してそのプロシージャを呼び出すことができます。

このシナリオに対応する方法で、このアイテムリストとアイテムベースをどのように構成しますか?これが私がこれまでに持っているものです:

  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また、コンストラクター内の個々のアイテムにリストオブジェクトを渡し、各アイテムにアイテムリストに自分自身を登録させるというアイデアもあります。この場合、アイテムリストには追加プロシージャがなく、おそらく別のリストオブジェクトも必要ありません。これには、私が見逃している大きなトリックがあるはずだと確信しています。これに効率的に対応するために、構造に大規模な変更を加えることもできます。

4

1 に答える 1

5

これは、ポリモーフィズムの古典的な使用法です。XE2のドキュメントによると(C++、ただしここで適用可能):

ポリモーフィック クラス: 同一のインターフェイスを提供するが、さまざまな特定の要件に対応するために実装できるクラスは、ポリモーフィック クラスと呼ばれます。クラスが少なくとも 1 つの仮想 (または純粋仮想) 関数を宣言または継承する場合、そのクラスは多態的です。

これは、あなたがやりたいことを正確に達成する例です。TBase各子孫が実装する必要がある抽象仮想メソッド ( ) を持つ基本型 ( ) と、それぞれ独自のメソッドを実装するDraw2 つの別個の子孫型 (TChildOneおよび) を作成します。TChildTwoDraw

10 個の項目 (定数を参照) と行を含む配列TBaseが宣言されています。配列は繰り返し処理され、現在のインデックスが奇数の場合は、子型のインスタンスが 1 つ作成されます。偶数の場合は、別の子型が作成されます。NumChildrenSetLength(BaseArray, NumChildren)

次に、配列が逆方向に繰り返され、ジェネリックTBase.Drawが呼び出されます。Drawこのコードは、呼び出されているクラス タイプに基づいて、異なる行プレフィックスを出力します。各配列項目の呼び出しは、 (そのインデックスで配列内にある型を確認せずに) をDraw呼び出すだけであることに注意してください。そのインデックス。TBase.DrawDraw

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.

コンソール ウィンドウへの出力は次のようになります。

ここに画像の説明を入力

于 2012-05-20T01:21:38.970 に答える