Delphiで循環ユニット参照を回避する方法はありますか?
たぶん、新しいバージョンのデルファイか、魔法のハックか何かですか?
私のデルファイ プロジェクトには、主にシングルトン クラスに基づく 100 000 行以上のコードがあります。これをリファクタリングする必要がありますが、それは数か月の「循環参照」地獄を意味します:)
Delphiで循環ユニット参照を回避する方法はありますか?
たぶん、新しいバージョンのデルファイか、魔法のハックか何かですか?
私のデルファイ プロジェクトには、主にシングルトン クラスに基づく 100 000 行以上のコードがあります。これをリファクタリングする必要がありますが、それは数か月の「循環参照」地獄を意味します:)
私は過去 10 年間、100 万行近くのレガシー コードを保守してきたので、あなたの苦労は理解できます。
私が管理しているコードで、循環使用に遭遇したとき、ユニット B で必要とされるユニット A の定数または型定義が原因であることがよくわかりました。 、グローバル変数) は、ユニット B にも必要です。
この状況では (運が良ければ!)、コードのこれらの部分を、定数、型定義、および共有コードを含む新しいユニット C に慎重に抽出できます。次に、ユニット A と B がユニット C を使用します。
私はソフトウェア設計の専門家ではなく、私よりもはるかに知識のある他の多くの人がここにいることを認識しているため、少し躊躇して上記を投稿します。とはいえ、私の経験が少しでもお役に立てば幸いです。
可能な限り実装セクションの uses を使用し、インターフェイス uses 句の内容を、インターフェイス宣言で表示する必要があるものに制限します。
「魔法のハック」はありません。循環参照は、コンパイラに無限ループを引き起こします (ユニット A は、ユニット B のコンパイルを必要とするユニット A のコンパイルを必要とするユニット B のコンパイルを必要とします)。
循環参照を避けることができないと思われる特定のインスタンスがある場合は、投稿を編集してコードを提供してください。ここにいる誰かが、それを修正する方法を理解するのを手伝ってくれると確信しています.
循環参照を回避する方法はたくさんあります。
デリゲート。あまりにも多くの場合、オブジェクトは、オブジェクト自体によって実行されるのではなく、イベントで実行されるべきコードを実行します。プロジェクトに取り組んでいるプログラマーの時間が短すぎた (私たちは常にそうではありませんか?)、十分な経験/知識を持っていなかった、または怠惰だったなどの理由で、このようなコードは最終的にアプリケーションに組み込まれます。実世界の例: メイン フォームがコンポーネントに "OnTCPActivity" プロシージャを登録する代わりに、アプリケーションの MainForm 上のビジュアル コンポーネントを直接更新する TCPSocket コンポーネント。
抽象クラス/インターフェース。それらのいずれかを使用すると、多くのユニット間の直接的な依存関係を取り除くことができます。依存関係を最大限に制限して、抽象クラスまたはインターフェイスを独自のユニットで単独で宣言できます。例: アプリケーションにはデバッグ フォームがあります。アプリケーションのさまざまな領域からの情報を表示するため、アプリケーション全体で使用されます。さらに悪いことに、デバッグ フォームの表示を許可するすべてのフォームは、デバッグ フォームからのすべてのユニットも要求することになります。より良いアプローチは、本質的に空のデバッグ フォームを持つことですが、「DebugFrames」を登録する能力があります。
TDebugFrm.RegisterDebugFrame(Frame : TDebugFrame);
このように、TDebugFrm には独自の依存関係がありません (TDebugFrame クラスを除いて)。デバッグ フォームを表示する必要があるすべてのユニットは、多くの依存関係を追加する危険を冒すことなく表示できます。
他にもたくさんの例があります... それだけで本を埋めることができるに違いありません。時間効率の良い方法でクリーンなクラス階層を設計するのはかなり難しく、経験が必要です。それを達成するために利用できるツールとその使用方法を知ることが、それを達成するための最初のステップです。しかし、あなたの質問に答えるには... あなたの質問に対する万能の答えはありません。それは常にケースバイケースで行われるべきです。
類似の質問: Delphi Enterprise: 循環参照なしでビジター パターンを適用するにはどうすればよいですか?
Uwe Raabe によって提示されたソリューションは、インターフェイスを使用して循環依存関係を解決します。
Modelmaker Code Explorerには、サイクルを含むすべての用途を一覧表示する非常に優れたウィザードがあります。
プロジェクトをコンパイルする必要があります。
それがデザインの問題であるという他のポスターに同意します。
デザインを注意深く見て、未使用のユニットを削除する必要があります。
DelphiLive'09 で、データベースとデータ認識コントロールを使用したよりスマートなコードというタイトルのセッションを行いました。これには、優れた設計に関するヒントがほとんど含まれていません (DB アプリに限定されません)。
--jeroen
以前の回答をしましたが、いくつかの考えとスクラッチの後、循環参照の問題を解決するより良い方法を見つけました。ここで、オブジェクト TB へのポインターが必要な最初のユニットは、ユニット B で定義されています。
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, b, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
FoB: TB;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
FoB := TB.Create(Self);
showmessage(FoB.owner.name);
end;
end.
ここで、TB が TForm1 にポインターを持つユニット B のコードを示します。
unit B;
interface
Uses
dialogs, Forms;
type
TForm1 = class(TForm);
TB = class
private
FaOwner: TForm1;
public
constructor Create(aOwner: TForm);
property owner: TForm1 read FaOwner;
end;
implementation
uses unit1;
Constructor TB.create(aOwner: TForm);
Begin
FaOwner := TForm1(aOwner);
FaOwner.Left := 500;
End;//Constructor
end.
そして、ここでそれがコンパイルされる理由。最初のユニット B は、実装セクションで Unit1 の使用を宣言します。Unit1 と Unit B の間の循環参照ユニットを即座に解決します。
しかし、Delphi がコンパイルできるようにするには、FaOwner の宣言である TForm1 を噛む何かを彼に与える必要があります。そのため、Unit1 の TForm1 の宣言に一致するスタブ クラス名 TForm1 を追加します。次に、コンストラクタを呼び出すときが来たら、TForm1 は自身にパラメータを渡すことができます。コンストラクター コードでは、aOwner パラメーターを Unit1.TForm1 に型キャストする必要があります。そしてほら、FaOwner が私のフォームを指すように設定しました。
これで、クラス TB が内部で FaOwner を使用する必要がある場合、毎回 Unit1.TForm1 に型キャストする必要はありません。これは、両方の宣言が同じであるためです。コンストラクターの宣言をに設定できることに注意してください
Constructor TB.create(aOwner: TForm1);
ただし、TForm1 がコンストラクターを呼び出し、パス自体にパラメーターがある場合は、b.TForm1 を使用して型キャストする必要があります。そうしないと、Delphi は両方の TForm1 に互換性がないことを示すエラーをスローします。したがって、TB.constructor を呼び出すたびに、適切な TForm1 に型キャストする必要があります。共通の祖先を使用する最初の解決策は、彼の方が優れています。型キャストを一度書いたら忘れてください。
投稿した後、両方の TForm1 が同一であると言い間違えたことに気付きました。それらは Unit1 ではありません。TForm1 には、B.TForm1 にとって未知のコンポーネントとメソッドがあります。Has long TB はそれらを使用する必要がないか、TForm によって与えられた共通性を使用する必要があるだけです。TB から UNit1.TForm1 に固有のものを呼び出す必要がある場合は、それを Unit1.TForm1 に型キャストする必要があります。
私はそれを試して、Delphi 2010 でテストしたところ、コンパイルして動作しました。
それが助けになり、頭痛を和らげることを願っています。