4

私は、それぞれの実装者クラスで抽象クラス参照を保持する TDictionary に基づいて、単純な依存性注入/制御システムの反転を書いています。

私の目標は次のとおりです。

  • タイプによる直接のインスタンス化は避けてください (明らかに)。
  • クラスのユニットを dpr に含めるだけで、それが登録され、di/ioc システムを介して選択およびインスタンス化できるようになります。
  • 実装セクションでのみ具体的な実装クラスを宣言します。
  • 初期化セクションの代わりにクラス コンストラクターを使用します。

ところで、クラス コンストラクターを使用してスマート リンクを利用することと、クラスを利用可能にするためにユニットを含めるだけで十分であることは、互いに打ち負かしていることを認識しています。他の理由でも、初期化セクションの代わりにクラス コンストラクターを使用したいと考えています。また、クラス コンストラクターと初期化セクションに分割するのではなく、すべてのクラスの初期化/登録コードをまとめておきたいと考えています。

問題

ファクトリへのクラスの登録は、クラス コンストラクターで行う必要があります。残念ながら、コンパイラは、独自のクラス コンストラクターでその型を使用するだけでは、クラスが "変更" されているとは見なしません。

登録関数を初期化セクションに入れると、コンパイラはクラスが変更されたと判断し、クラス コンストラクターを呼び出します。しかし、それは、すべてのクラス初期化コードをクラス コンストラクターに保持するという私の演習の目的を無効にします。

2 つの質問

  • コンパイラーは、独自のクラスコンストラクターでのクラスの使用を「クラスに触れる」と見なすべきですか、それともコンパイラーが行うことを期待するには多すぎますか?
  • 初期化セクションを使用せずに目標を達成する方法について、賢いアイデアを持っている人はいますか?

アプリケーションで使用される抽象クラス:

TSite = class abstract (TObject)
  function GetURL: string; virtual; abstract;
  property URL: string read GetURL;
end;

TSites = class (TList<TSite>);

TThisApplication = class abstract (TObject)
  function Sites: TSites; virtual; abstract;
end;

TThisApplication の具体的な実装クラス (実装セクションで宣言されています!)

  TThisApplicationConcrete = class(TThisApplication)
  class constructor ClassCreate;
  strict private
    FSites: TSites;
    function Sites: TSites; override;
  end;

class constructor TThisApplicationConcrete.ClassCreate;
begin
  RegisterImplementorClass(TThisApplication, TThisApplicationConcrete);
end;

function TThisApplicationConcrete.Sites: TSites;
var
  SiteList: TSites;
begin
  if not Assigned(FSites) then begin
    SiteList := TSites.Create;  // Change to use factory
    //RetrieveSites(SiteList);
    FSites := SiteList;
  end;

  Result := FSites;
end;

TThisApplication のインスタンスを取得する関数:

function ThisApplication: TThisApplication;
var
  ImplementorClass: TClass;
begin
  ImplementorClass := GetImplementorClass(TThisApplication);
  if Assigned(ImplementorClass) then begin
    Result := ImplementorClass.Create as TThisApplication;
  end else begin
    Result := nil;
  end;
end;

これは現在、別の関数でコーディングされていますが、工場に移動することができます。

完全なサンプルコード

誰かが実験したい場合は、テスト プロジェクトの完全なコードをhttp://www.bjsoftware.com/delphistuff/stackoverdlow/classconstructors.zipで入手できます。

zip の内容:

  • 4 つのプロジェクトはすべて同じソース ファイルを使用し、条件定義のみが異なります (そのため、dproj も含まれています)
  • 4 つのソース ファイル
  • groupproj とその dsk と 4 つのプロジェクトすべて
  • 4 つのプロジェクトすべてを実行する RunTestApps.cmd
  • RunTestApps.cmd の実行結果を含む Results.txt
  • この質問のテキストを含む WriteUp.txt

すべての dcu と exe がソース ディレクトリに移動するため、常に "Build All Projects" を実行する必要があることに注意してください。そうしないと、多くのエラーや混乱に直面することになります。その名前が示すことを行います。

4

3 に答える 3

8

これは予想通りです。Uwe が指摘したように、自己参照型のクラス コンストラクターはインクルージョンをトリガーするのに十分ではありません。初期化セクションに参照を配置すると、クラス自体の外部にあるため、うまくいきます。包含のためにクラスを自己参照しようとすることは、自分のサスペンダーを引っ張って深い穴から抜け出そうとすることに似ています。

于 2011-06-03T20:16:36.303 に答える
7

クラス自体の外部でクラスを使用しない限り、コンパイラはこれをまったく使用されていないクラスと見なし、クラス コンストラクターを呼び出しません。したがって、コードを微調整してコンパイラをだますのではなく、初期化セクションを使用する必要があると思います。独断的なアプローチではなく、実用的なアプローチをとってください。とにかく読みやすくなります。

于 2011-06-03T19:55:06.133 に答える
2

クラスコンストラクターが設計されたものとは異なる動作をすることを期待していると思います。クラス コンストラクターは、最初の作成の「直前」に呼び出されません。(少なくともWIN32デルファイではありません)。クラスが参照されている場合、そのクラス コンストラクターはユニットの初期化コードの前に実行されます。

クラスへの唯一の参照がたまたまそのクラス内にある場合、アプリケーションに実際にリンクされているコードはそのクラスを参照していません。したがって、そのクラス コンストラクターが呼び出されることはありません。

レジスタ関数は初期化セクションに属していると思います。そこに登録機能があると、クラスコンストラクターも強制的に実行されます。登録コードをクラスコンストラクターに配置する必要がある理由がわかりません。

クラス コンストラクター/デストラクターのしくみについて詳しく知りたい場合は、Allen Bauer による次の非常に優れた記事を読むことができます。

http://blogs.embarcadero.com/abauer/2009/09/04/38899

于 2011-06-03T20:17:54.453 に答える