10

分散型モジュール登録の良い解決策を探しています。

プロジェクトのすべてのモジュール ユニットを使用する単一のユニットは必要ありませんが、モジュール ユニット自体を登録できるようにしたいと考えています。

initialization私が考えることができる唯一の解決策は、Delphi ユニットに依存することです。

私はテストプロジェクトを書きました:

ユニット2

TForm2 = class(TForm)
private
  class var FModules: TDictionary<string, TFormClass>;
public
  class property Modules: TDictionary<string, TFormClass> read FModules;
  procedure Run(const AName: string);
end;

procedure TForm2.Run(const AName: string);
begin
  FModules[AName].Create(Self).ShowModal;
end;

initialization
  TForm2.FModules := TDictionary<string, TFormClass>.Create;

finalization
  TForm2.FModules.Free;

ユニット3

TForm3 = class(TForm)

implementation

uses
  Unit2;

initialization   
  TForm2.Modules.Add('Form3', TForm3);

4号機

TForm4 = class(TForm)

implementation

uses
  Unit2;

initialization   
  TForm2.Modules.Add('Form4', TForm4);

ただし、これには 1 つの欠点があります。登録単位 (この場合はUnit2s)initializationセクションが常に最初に実行されることが保証されていますか?

セクションに関する警告をよく読んでいますが、initializationセクションで例外を発生させないようにする必要があることを知っています。

4

4 に答える 4

6

私の答えは、 NGLN の答えとはまったく対照的です。ただし、私の推論を真剣に検討することをお勧めします。そうすれば、まだ を使用したいと思っていてもinitialization、潜在的な落とし穴や推奨される予防措置に目を向けることはできません。


モジュールの登録に初期化セクションを使用するのは良い考えですか?

残念ながら、NGLN の賛成論は、お気に入りのロックスターがそうしたかどうかに基づいてドラッグをすべきかどうかを議論することに少し似ています。

引数は、機能の使用がコードの保守性にどのように影響するかに基づいている必要があります。

  • プラス面としては、ユニットを含めるだけでアプリケーションに機能を追加できます。(良い例は、例外ハンドラー、ロギング フレームワークです。)
  • マイナス面としては、ユニットを含めるだけでアプリケーションに機能を追加できます。(あなたが意図したかどうかにかかわらず。)

「プラス」ポイントが「マイナス」ポイントと見なされる理由の実際の例をいくつか示します。

  1. 検索パスを介していくつかのプロジェクトに含まれていたユニットがありました。initializationこのユニットは、セクションで自己登録を行いました。いくつかのユニットの依存関係を再配置、リファクタリングのビットが行われました。次に、ユニットがアプリケーションの 1 つに含まれなくなり、その機能の 1 つが壊れました。

  2. サードパーティの例外ハンドラーを変更したかったのです。簡単に思えます: プロジェクト ファイルから古いハンドラーのユニットを取り出し、新しいハンドラーのユニットを追加します。問題は、いくつかの古いハンドラーのユニットを直接参照するユニットがいくつかあったことです。
    最初に例外フックを登録したのはどの例外ハンドラーだと思いますか? 正しく登録されたのは?

ただし、はるかに深刻な保守性の問題があります。そして、それはユニットが初期化される順序の予測可能性です。ユニットが初期化 (および終了) する順序を厳密に決定する規則はありますが、プログラマーが最初の数ユニットを超えてこれを正確に予測することは非常に困難です。

initializationこれは明らかに、他のユニットの初期化に依存するセクションに深刻な影響を及ぼします。たとえば、initializationセクションの 1 つにエラーがあり、例外ハンドラー/ロガーが初期化される前にエラーが呼び出された場合にどうなるかを考えてみましょう...アプリケーションの起動に失敗し、理由を理解する。


登録ユニット (この場合は Unit2s) の初期化セクションが常に最初に実行されることが保証されていますか?

これは、Delphi のドキュメントが単純に間違っている多くのケースの 1 つです。

インターフェイス usesリスト内のユニットの場合、クライアントによって使用されるユニットの初期化セクションは、ユニットがクライアントのuses句に表示される順序で実行されます。

次の 2 つの単位を考えてみましょう。

unit UnitY;

interface

uses UnitA, UnitB;
...

unit UnitX;

interface

uses UnitB, UnitA;
... 

したがって、両方のユニットが同じプロジェクトにある場合、(ドキュメントによると): のUnitA前に初期化UnitB AND UnitBの前に初期化しUnitAます。これは明らかに不可能です。したがって、実際の初期化シーケンスは、他の要因にも依存する可能性があります。A または B を使用する他のユニット X と Y が初期化される順序。

したがって、ドキュメントを支持する最良のケースの議論は次のとおりです。説明を簡単にするために、いくつかの重要な詳細が省略されています。ただし、実際の状況では、それは単に間違っているという結果になります。

はい、理論的には句を微調整して、特定の初期化シーケンスを保証することができます。usesしかし、現実には、何千ものユニットを含む大規模なプロジェクトでは、これを実行するのは人間的に非現実的であり、簡単に破ることができます。


セクションに対する他の議論がありinitializationます:

  • 通常、初期化が必要になるのは、グローバルに共有されたエンティティがあるためだけです。グローバル データが悪い考えである理由を説明する資料はたくさんあります。
  • 初期化のエラーは、デバッグが難しい場合があります。アプリケーションの起動にまったく失敗する可能性があるクライアント マシンではなおさらです。初期化を明示的に制御すると、少なくとも最初に、何か失敗した場合に何が問題なのかをユーザーに伝えることができる状態にアプリケーションがあることを確認できます。
  • テストプロジェクトにユニットを含めるだけで副作用が発生するため、初期化セクションはテスト容易性を妨げます。また、このユニットに対するテスト ケースがある場合、各テストはほぼ確実にグローバルな変更を他のテストに "リーク" するため、それらはおそらく密結合になります。

結論

すべての依存関係を引き込む「神のユニット」を避けたいというあなたの願望を理解しています。しかし、アプリケーション自体は、すべての依存関係を定義し、それらをまとめて、要件に従って連携させるものではないでしょうか? 特定のユニットをその目的に捧げることに害はないと思います。追加のボーナスとして、単一のエントリ ポイントからすべてを実行すると、起動シーケンスのデバッグがはるかに簡単になります。

それでも を利用したい場合は、initialization次のガイドラインに従うことをお勧めします。

  • これらのユニットがプロジェクトに明示的に含まれていることを確認してください。ユニットの依存関係の変更により、誤って機能を壊したくありません。
  • セクションに順序依存性がまったくないようにする必要がありますinitialization。(残念ながら、あなたの質問はこの時点での失敗を暗示しています。)
  • finalizationまた、セクションに順序依存があってはなりません。(Delphi 自体には、この点に関していくつかの問題があります。1 つの例はComObjです。ファイナライズが早すぎると、COM サポートが初期化されなくなり、シャットダウン中にアプリケーションが失敗する可能性があります。)
  • アプリケーションの実行とデバッグに絶対に不可欠と思われるものを決定し、DPR ファイルの先頭からそれらの初期化シーケンスを確認します。
  • テスト容易性のために、初期化を「オフ」にするか、できれば完全に無効にできることを確認してください。
于 2014-03-30T17:48:11.147 に答える
3

class contructorsclass destructors同様に使用できます:

TModuleRegistry = class sealed
private
  class var FModules: TDictionary<string, TFormClass>;
public
  class property Modules: TDictionary<string, TFormClass> read FModules;
  class constructor Create;
  class destructor Destroy;
  class procedure Run(const AName: string); static;
end;

class procedure TModuleRegistry.Run(const AName: string);
begin
  // Do somthing with FModules[AName]
end;

class constructor TModuleRegistry.Create;
begin
  FModules := TDictionary<string, TFormClass>.Create;
end;

class destructor TModuleRegistry.Destroy;
begin
  FModules.Free;
end;

TModuleRegistryインスタンス メンバーを持たないため、シングルトンです。

コンパイラは、class constructorが常に最初に呼び出されるようにします。

これは、@SpeedFreakの回答と非常によく似たものにクラスメソッドRegisterと組み合わせることができます。Unregister

于 2014-03-30T13:09:27.513 に答える