[英]Is it a good idea to use initialization sections for module registration?
我正在為分散式模塊注冊尋找一個好的解決方案。
我不想要一個使用項目所有模塊單元的單元,但我寧願讓模塊單元自己注冊。
我能想到的唯一解決方案是依賴於Delphi單元的initialization
。
我寫了一個測試項目:
單元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;
UNIT3
TForm3 = class(TForm)
implementation
uses
Unit2;
initialization
TForm2.Modules.Add('Form3', TForm3);
UNIT4
TForm4 = class(TForm)
implementation
uses
Unit2;
initialization
TForm2.Modules.Add('Form4', TForm4);
這有一個缺點。 是否保證我的注冊單元(在本例中為Unit2
) initialization
部分始終先運行?
我經常閱讀有關initialization
部分的警告,我知道我必須避免在其中引發異常。
我會使用以下“模式”:
unit ModuleService;
interface
type
TModuleDictionary = class(TDictionary<string, TFormClass>);
IModuleManager = interface
procedure RegisterModule(const ModuleName: string; ModuleClass: TFormClass);
procedure UnregisterModule(const ModuleName: string);
procedure UnregisterModuleClass(ModuleClass: TFormClass);
function FindModule(const ModuleName: string): TFormClass;
function GetEnumerator: TModuleDictionary.TPairEnumerator;
end;
function ModuleManager: IModuleManager;
implementation
type
TModuleManager = class(TInterfacedObject, IModuleManager)
private
FModules: TModuleDictionary;
public
constructor Create;
destructor Destroy; override;
// IModuleManager
procedure RegisterModule(const ModuleName: string; ModuleClass: TFormClass);
procedure UnregisterModule(const ModuleName: string);
procedure UnregisterModuleClass(ModuleClass: TFormClass);
function FindModule(const ModuleName: string): TFormClass;
function GetEnumerator: TModuleDictionary.TPairEnumerator;
end;
procedure TModuleManager.RegisterModule(const ModuleName: string; ModuleClass: TFormClass);
begin
FModules.AddOrSetValue(ModuleName, ModuleClass);
end;
procedure TModuleManager.UnregisterModule(const ModuleName: string);
begin
FModules.Remove(ModuleName);
end;
procedure TModuleManager.UnregisterModuleClass(ModuleClass: TFormClass);
var
Pair: TPair<string, TFormClass>;
begin
while (FModules.ContainsValue(ModuleClass)) do
begin
for Pair in FModules do
if (ModuleClass = Pair.Value) then
begin
FModules.Remove(Pair.Key);
break;
end;
end;
end;
function TModuleManager.FindModule(const ModuleName: string): TFormClass;
begin
if (not FModules.TryGetValue(ModuleName, Result)) then
Result := nil;
end;
function TModuleManager.GetEnumerator: TModuleDictionary.TPairEnumerator;
begin
Result := FModules.GetEnumerator;
end;
var
FModuleManager: IModuleManager = nil;
function ModuleManager: IModuleManager;
begin
// Create the object on demand
if (FModuleManager = nil) then
FModuleManager := TModuleManager.Create;
Result := FModuleManager;
end;
initialization
finalization
FModuleManager := nil;
end;
單元2
TForm2 = class(TForm)
public
procedure Run(const AName: string);
end;
implementation
uses
ModuleService;
procedure TForm2.Run(const AName: string);
var
ModuleClass: TFormClass;
begin
ModuleClass := ModuleManager.FindModule(AName);
ASSERT(ModuleClass <> nil);
ModuleClass.Create(Self).ShowModal;
end;
UNIT3
TForm3 = class(TForm)
implementation
uses
ModuleService;
initialization
ModuleManager.RegisterModule('Form3', TForm3);
finalization
ModuleManager.UnregisterModuleClass(TForm3);
end.
UNIT4
TForm4 = class(TForm)
implementation
uses
ModuleService;
initialization
ModuleManager.RegisterModule('Form4', TForm4);
finalization
ModuleManager.UnregisterModule('Form4');
end.
我的回答與NGLN的答案形成鮮明對比。 但是,我建議你認真考慮我的推理。 然后,即使你仍然希望使用initialization
,至少你的眼睛會對潛在的陷阱和建議的預防措施開放。
使用初始化部分進行模塊注冊是個好主意嗎?
不幸的是,NGLN贊成的論點有點像是在爭論你是否應該根據你最喜歡的搖滾明星是否這樣做而吸毒。
爭論應該基於該功能的使用如何影響代碼可維護性。
為什么“加”點也可以被認為是“減”點的幾個現實世界的例子:
我們有一個單元通過搜索路徑包含在一些項目中。 該單元在initialization
部分中執行自注冊。 進行了一些重構,重新排列了一些單元依賴關系。 接下來,該單元不再被包含在我們的一個應用程序中,打破了它的一個功能。
我們想要更改我們的第三方異常處理程序。 聽起來很容易:將舊處理程序的單元從項目文件中取出,並添加新處理程序的單位。問題是我們有一些單元有自己的直接引用一些舊處理程序的單元。
您認為哪個異常處理程序首先注冊了它的異常掛鈎? 哪個注冊正確?
但是,存在更嚴重的可維護性問題。 這就是單位初始化順序的可預測性。 盡管有一些規則可以嚴格確定單元初始化(並最終確定)的順序,但作為程序員,您很難准確地預測出前幾個單元之外的單元。
對於任何依賴於其他單元初始化的initialization
部分,這顯然會產生嚴重后果。 例如,考慮如果你的一個initialization
部分中有錯誤會發生什么,但它恰好在你的異常處理程序/ logger初始化之前被調用...你的應用程序將無法啟動,你將陷入困境找出原因。
是否保證我的注冊單元(在本例中為Unit2s)初始化部分始終先運行?
這是Delphi的文檔完全錯誤的許多案例之一。
對於接口使用列表中的單元,客戶端使用的單元的初始化部分按照單元出現在客戶端的uses子句中的順序執行。
考慮以下兩個單元:
unit UnitY;
interface
uses UnitA, UnitB;
...
unit UnitX;
interface
uses UnitB, UnitA;
...
因此,如果兩個單元都在同一個項目,然后(根據文檔): UnitA
前初始化UnitB
和 UnitB
之前初始化UnitA
。 這顯然是不可能的 。 因此,實際的初始化順序也可能取決於其他因素:使用A或B的其他單位.X和Y初始化的順序。
因此,支持文檔的最佳案例論點是:為了使解釋簡單,省略了一些基本細節。 然而,效果是,在現實世界的情況下,這是完全錯誤的。
是的,你“理所當然” 可以微調你的uses
條款,以保證特定的初始化順序。 然而,現實情況是,在一個擁有數千個單位的大型項目中,這樣做是非常不切實際的,而且太容易破解。
還有其他反對initialization
部分的參數:
我理解你希望避免引入所有依賴關系的“神單位”。 但是,應用程序本身不是定義所有依賴項的東西,將它們組合在一起並使它們根據需求進行協作嗎? 我認為將特定單位專門用於此目的並不會造成任何傷害。 作為一個額外的好處,如果從單個入口點完成所有操作,則調試啟動序列要容易得多。
但是,如果您仍想使用initialization
,我建議您遵循以下准則:
initialization
部分中必須絕對沒有順序依賴。 (不幸的是,你的問題意味着此時失敗了。) finalization
部分中也必須沒有訂單依賴。 (Delphi本身在這方面存在一些問題。一個例子是ComObj
。如果它太快完成,它可能會使COM支持無法初始化並導致應用程序在關閉期間失敗。) 您也可以使用class contructors
class destructors
和class 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
。
這可以與Register
和Unregister
類方法結合使用,與@SpeedFreak的答案非常相似。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.