Jen*_*off 10 delphi module delphi-units initialization-order delphi-xe3
我正在为分散式模块注册寻找一个好的解决方案.
我不想要一个使用项目所有模块单元的单元,但我宁愿让模块单元自己注册.
我能想到的唯一解决方案就是依赖initializationDelphi单元.
我写了一个测试项目:
单元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;
Run Code Online (Sandbox Code Playgroud)
UNIT3
TForm3 = class(TForm)
implementation
uses
Unit2;
initialization
TForm2.Modules.Add('Form3', TForm3);
Run Code Online (Sandbox Code Playgroud)
UNIT4
TForm4 = class(TForm)
implementation
uses
Unit2;
initialization
TForm2.Modules.Add('Form4', TForm4);
Run Code Online (Sandbox Code Playgroud)
这有一个缺点.是否保证我的注册单元(在本例中为Unit2s)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;
Run Code Online (Sandbox Code Playgroud)
单元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;
Run Code Online (Sandbox Code Playgroud)
UNIT3
TForm3 = class(TForm)
implementation
uses
ModuleService;
initialization
ModuleManager.RegisterModule('Form3', TForm3);
finalization
ModuleManager.UnregisterModuleClass(TForm3);
end.
Run Code Online (Sandbox Code Playgroud)
UNIT4
TForm4 = class(TForm)
implementation
uses
ModuleService;
initialization
ModuleManager.RegisterModule('Form4', TForm4);
finalization
ModuleManager.UnregisterModule('Form4');
end.
Run Code Online (Sandbox Code Playgroud)
我的回答与NGLN的答案形成鲜明对比.但是,我建议你认真考虑我的推理.然后,即使你仍然希望使用initialization,至少你的眼睛会对潜在的陷阱和建议的预防措施开放.
使用初始化部分进行模块注册是个好主意吗?
不幸的是,NGLN赞成的论点有点像是在争论你是否应该根据你最喜欢的摇滚明星是否这样做而吸毒.
争论应该基于该功能的使用如何影响代码可维护性.
为什么"加"点也可以被认为是"减"点的几个现实世界的例子:
我们有一个单元通过搜索路径包含在一些项目中.该部门在该initialization部分进行了自我登记.进行了一些重构,重新排列了一些单元依赖关系.接下来,该单元不再被包含在我们的一个应用程序中,打破了它的一个功能.
我们想要更改我们的第三方异常处理程序.听起来很容易:将旧处理程序的单元从项目文件中取出,并添加新处理程序的单位.问题是我们有一些单元有自己的直接引用一些旧处理程序的单元.
您认为哪个异常处理程序首先注册了它的异常挂钩?哪个注册正确?
但是,存在更严重的可维护性问题.这就是单位初始化顺序的可预测性.尽管有一些规则可以严格确定单元初始化(并最终确定)的顺序,但作为程序员,您很难准确地预测出前几个单元之外的情况.
对于initialization依赖于其他单位初始化的任何部分,这显然会产生严重后果.例如,考虑如果你的某个initialization部分中有错误会发生什么,但是在你的异常处理程序/ logger初始化之前碰巧会调用它...你的应用程序将无法启动,你将陷入困境搞清楚为什么.
是否保证我的注册单元(在本例中为Unit2s)初始化部分始终先运行?
这是Delphi的文档完全错误的许多案例之一.
对于接口使用列表中的单元,客户端使用的单元的初始化部分按照单元出现在客户端的uses子句中的顺序执行.
考虑以下两个单元:
unit UnitY;
interface
uses UnitA, UnitB;
...
unit UnitX;
interface
uses UnitB, UnitA;
...
Run Code Online (Sandbox Code Playgroud)
因此,如果两个单元都在同一个项目中,那么(根据文档):UnitA在UnitB AND UnitB之前初始化之前的初始化UnitA.这显然是不可能的.因此,实际的初始化顺序也可能取决于其他因素:使用A或B的其他单位.X和Y初始化的顺序.
因此,支持文档的最佳案例论点是:为了使解释简单,省略了一些基本细节.然而,效果是,在现实世界的情况下,这是完全错误的.
是的,您" 可以 "理论上微调您的uses子句以保证特定的初始化序列.然而,现实情况是,在一个拥有数千个单位的大型项目中,这样做是非常不切实际的,而且太容易破解.
对于initialization部分还有其他参数:
我理解你希望避免引入所有依赖关系的"神单位".但是,应用程序本身不是定义所有依赖项的东西,将它们组合在一起并使它们根据需求进行协作吗?我认为将特定单位专门用于此目的并不会造成任何伤害.作为一个额外的好处,如果从单个入口点完成所有操作,则调试启动序列要容易得多.
但是,如果您仍想使用initialization,我建议您遵循以下准则:
initialization部分必须绝对没有订单依赖.(不幸的是,你的问题意味着此时失败了.)finalization部分中也必须没有订单依赖项.(Delphi本身在这方面存在一些问题.一个例子是ComObj.如果它太快完成,它可能会使COM支持无法初始化并导致应用程序在关闭期间失败.)| 归档时间: |
|
| 查看次数: |
4966 次 |
| 最近记录: |