15

私は公式ドキュメントを読み、クラス参照とは何かを理解していますが、それらが代替手段と比較して最適な解決策である時期と理由を理解していません。

ドキュメントで引用されている例は、TCollectionItem の任意の子孫でインスタンス化できる TCollection です。クラス参照を使用する正当な理由は、コンパイル時に型が不明なクラス メソッドを呼び出すことができるためです (これは TCollection のコンパイル時であると想定しています)。TCollectionItemClass を引数として使用することが、TCollectionItem を使用するよりも優れていることを理解していません。TCollection は引き続き TCollectionItem の子孫を保持でき、TCollectionItem で宣言された任意のメソッドを呼び出すことができます。そうじゃない?

これを一般的なコレクションと比較してください。TObjectList は、TCollection とほぼ同じ機能を提供するように見えますが、タイプ セーフの利点が追加されています。オブジェクト タイプを格納するために TCollectionItem から継承する必要がなくなり、必要に応じてタイプ固有のコレクションを作成できます。また、コレクション内からアイテムのメンバーにアクセスする必要がある場合は、一般的な制約を使用できます。Delphi 2009 より前のプログラマがクラス参照を利用できるという事実以外に、ジェネリック コンテナよりもクラス参照を使用する説得力のある理由はありますか?

ドキュメントに示されている他の例は、オブジェクト ファクトリとして機能する関数にクラス参照を渡すことです。この場合、TControl 型のオブジェクトを作成するためのファクトリです。あまり明白ではありませんが、TControl ファクトリは、TControl のコンストラクタではなく、渡された子孫型のコンストラクタを呼び出していると想定しています。この場合、少なくともクラス参照を使用する何らかの理由が見えてきます。

だから私が本当に理解しようとしているのは、いつ、どこでクラス参照が最も適切で、開発者は何を買うのかということだと思います.

4

5 に答える 5

26

メタクラスと「クラスプロシージャ」

メタクラスはすべて「クラスプロシージャ」に関するものです。基本から始めるclass

type
  TAlgorithm = class
  public
    class procedure DoSomething;virtual;
  end;

DoSomethingは、 TALgorithmのインスタンスなしで呼び出すことができるためですclass procedure(他のグローバルプロシージャと同じように動作します)。できるよ:

TAlgorithm.DoSomething; // this is perfectly valid and doesn't require an instance of TAlgorithm

この設定が完了したら、いくつかの代替アルゴリズムを作成し、すべてが基本アルゴリズムの一部を共有します。このような:

type
  TAlgorithm = class
  protected
    class procedure DoSomethingThatAllDescendentsNeedToDo;
  public
    class procedure DoSomething;virtual;
  end;

  TAlgorithmA = class(TAlgorithm)
  public
    class procedure DoSomething;override;
  end;

  TAlgorithmB = class(TAlgorithm)
  public
    class procedure DoSomething;override;
  end;

これで、1つの基本クラスと2つの子孫クラスができました。次のコードは、メソッドを「クラス」メソッドとして宣言したため、完全に有効です。

TAlgorithm.DoSomething;
TAlgorithmA.DoSomething;
TAlgorithmB.DoSomething;

タイプを紹介しましょうclass of

type
  TAlgorithmClass = class of TAlgorithm;

procedure Test;
var X:TAlgorithmClass; // This holds a reference to the CLASS, not a instance of the CLASS!
begin
  X := TAlgorithm; // Valid because TAlgorithmClass is supposed to be a "class of TAlgorithm"
  X := TAlgorithmA; // Also valid because TAlgorithmA is an TAlgorithm!
  X := TAlgorithmB;
end;

TAlgorithmClassは、他のデータ型と同じように使用できるデータ型であり、変数に格納して、パラメーターとして関数に渡すことができます。言い換えれば、これを行うことができます:

procedure CrunchSomeData(Algo:TAlgorithmClass);
begin
  Algo.DoSomething;
end;

CrunchSomeData(TAlgorithmA);

この例では、プロシージャCrunchSomeDataは、TAlgorithmの子孫である限り、アルゴリズムの任意のバリエーションを使用できます。

この動作が実際のアプリケーションでどのように使用されるかの例を次に示します。法によって定義されたアルゴリズムに従っていくつかの数値を計算する必要がある給与タイプのアプリケーションを想像してみてください。法則が変更されることがあるため、このアルゴリズムは時間とともに変更されると考えられます。私たちのアプリケーションは、現在の年(最新の計算機を使用)と他の年の両方の給与を、古いバージョンのアルゴリズムを使用して計算する必要があります。物事は次のようになります。

// Algorithm definition
TTaxDeductionCalculator = class
public
  class function ComputeTaxDeduction(Something, SomeOtherThing, ThisOtherThing):Currency;virtual;
end;

// Algorithm "factory"
function GetTaxDeductionCalculator(Year:Integer):TTaxDeductionCalculator;
begin
  case Year of
    2001: Result := TTaxDeductionCalculator_2001;
    2006: Result := TTaxDeductionCalculator_2006;
    else Result := TTaxDeductionCalculator_2010;
  end;
end;

// And we'd use it from some other complex algorithm
procedure Compute;
begin
  Taxes := (NetSalary - GetTaxDeductionCalculator(Year).ComputeTaxDeduction(...)) * 0.16;
end;

仮想コンストラクター

Delphiコンストラクタは「クラス関数」のように機能します。メタクラスがあり、メタクラスが仮想コンストラクターについて知っている場合、子孫型のインスタンスを作成できます。これは、「追加」ボタンを押したときに新しいアイテムを作成するためにTCollectionのIDEエディターによって使用されます。これを機能させるために必要なすべてのTCollectionは、TCollectionItemのMetaClassです。

于 2010-07-30T20:26:22.713 に答える
8

はい、コレクションは引き続き TCollectionItem の子孫を保持し、そのメソッドを呼び出すことができます。しかし、TCollectionItem の子孫の新しいインスタンスをインスタンス化することはできません。TCollectionItem.Create を呼び出すと、TCollectionItem のインスタンスが構築されますが、

private
  FItemClass: TCollectionItemClass;
...

function AddItem: TCollectionItem;
begin
  Result := FItemClass.Create;
end;

FItemClass に保持されている TCollectionItem の子孫のクラスのインスタンスを構築します。

私はジェネリック コンテナーをあまり使用していませんが、選択肢があれば、ジェネリック コンテナーを選ぶと思います。しかし、どちらの場合でも、リストをインスタンス化したい場合はメタクラスを使用する必要があり、コンテナにアイテムが追加されたときに必要な処理を行う必要があり、事前に正確なクラスがわかりません。

たとえば、監視可能な TObjectList の子孫 (またはジェネリック コンテナ) は、次のようなものを持つことができます。

function AddItem(aClass: TItemClass): TItem;
begin
  Result := Add(aClass.Create);
  FObservers.Notify(Result, cnNew);
  ...
end;

要するに、メタクラスの利点/利点は、の知識しかないメソッド/クラスであると思います

type
  TMyThing = class(TObject)
  end;
  TMyThingClass = class of TMyThing;

TMyThing の任意の子孫のインスタンスを、宣言されている場所に構築できます。

于 2010-07-30T16:00:29.340 に答える
4

ジェネリックは非常に便利であり、TObjectList<T>(通常) よりも便利であることに同意しTCollectionます。ただし、クラス参照はさまざまなシナリオでより役立ちます。それらは実際には異なるパラダイムの一部です。たとえば、クラス参照は、オーバーライドする必要がある仮想メソッドがある場合に役立ちます。仮想メソッドのオーバーライドはオリジナルと同じシグネチャを持つ必要があるため、ここではジェネリック パラダイムは適用されません。

クラス参照が頻繁に使用される場所の 1 つは、フォーム ストリーミングです。DFM をテキストとして表示すると、すべてのオブジェクトが名前とクラスで参照されていることがわかります。(実際、名前はオプションです。) フォーム リーダーがオブジェクト定義の最初の行を読み取ると、クラスの名前が取得されます。ルックアップ テーブルで検索してクラス参照を取得し、そのクラス参照を使用してそのクラスのオーバーライドを呼び出してTComponent.Create(AOwner: TComponent)、適切な種類のオブジェクトをインスタンス化できるようにし、DFM に記述されているプロパティの適用を開始します。これは、クラス参照によって得られるものであり、ジェネリックでは実現できません。

于 2010-07-30T15:46:11.573 に答える
1

また、ハードコードされた 1 つのクラスだけでなく、基本クラスから継承する任意のクラスを構築できるファクトリを作成できるようにする必要がある場合は常に、メタクラスを使用します。

ただし、メタクラスという用語は、Delphi の世界ではなじみがありません。私たちはそれらをクラス参照と呼んでいると思いますが、これは「魔法の」響きの少ない名前を持っているため、質問に両方の一般的なモニカを入れていただければ幸いです。

これがよく使われている場所の具体的な例は、JVCL JvDocking コンポーネントで、「ドッキング スタイル」コンポーネントがベース ドッキング コンポーネント セットにメタクラス情報を提供するため、ユーザーがマウスをドラッグしてクライアント フォームをドッキング ホスト フォーム、グラバー バーを表示する「タブ ホスト」および「コンジョイン ホスト」フォーム (通常のドッキングされていないウィンドウのタイトル バーに似た外観) は、カスタマイズされた外観を提供するユーザー定義のプラグイン クラスにすることができます。プラグインベースでカスタマイズされたランタイム機能。

于 2010-07-30T16:17:23.220 に答える
0

一部のアプリケーションには、クラスを1つ以上のクラスのインスタンスを編集できるフォームに接続するメカニズムがあります。これらのペアが保存されている中央のリストがあります。クラス参照とフォームクラス参照です。したがって、クラスのインスタンスがある場合、対応するフォームクラスを検索し、そこからフォームを作成して、インスタンスを編集させることができます。

もちろん、これは適切なフォームクラスを返すクラスメソッドを使用して実装することもできますが、その場合はフォームクラスがクラスによって認識されている必要があります。私のアプローチは、よりモジュール化されたシステムを作ります。フォームはクラスを認識している必要がありますが、その逆はできません。これは、クラスを変更できない場合の重要なポイントになる可能性があります。

于 2010-07-30T15:55:26.227 に答える