我有一个基本抽象类,我在其中定义了一个虚拟抽象函数。
TMyBaseClass = class abstract(TForm)
public
function MyFunction : string; virtual; abstract;
end;
我注意到编译非抽象后代类时没有显示错误/警告。
TMyDescendantClass = class(TMyBaseClass);
目标是强迫我的同事为每个后代类实现该功能。我发现了一个类似的问题(Delphi 6: Force compiler error on missing abstract class methods?),但解决方案不符合我的目标。
UPDATE1:
通过调用“GetClass”函数获得类。
GetClass('TMyDescendantClass').Create
由于这个事实,实例创建不会引起警告。
答案 0 :(得分:7)
如果您坚持使用当前的设计,除了在运行时等待错误之外,您无能为力。
如果您静态引用这些类,那么如果您实例化包含抽象方法的类,则会看到警告。但是因为你动态获取类,编译器无法帮助你。
在课程中添加界面不会有多大用处。假设您更改了类,以便实现接口?您必须在基类中实现它。你会怎么做?通过使用虚拟方法?你只是回到你开始的地方。
假设您可以某种方式强制派生类的实现者实现所有这些抽象方法。是什么阻止他们这样做:
type
TMyDescendantClass = class(TMyBaseClass)
public
function MyFunction: string; override;
end;
function TMyDescendantClass.MyFunction: string;
begin
// TODO: implement this method
end;
有时合同无法在编译时强制执行!
而不是使用类的抽象方法,你可以完全改变大头钉。不要求实施者从基类派生出来。当您提出要求时,请他们为您提供界面。这就把责任推到了他们身上。他们必须实现接口(并实例化它)。然后他们有义务实现每个功能。并不是说它阻止他们当然不正确地实现这些功能!
根据您所知道的因素,这可能不如您当前的设计方便。但只有你可以决定。
答案 1 :(得分:3)
一个有点丑陋的解决方案会在早期触发运行时异常,从而排除对其后代类的任何使用,除非他们执行了实现:
TMyBaseClass = class
public function MyFunction : string; virtual; abstract;
public procedure AfterConstruction; override;
end;
....
procedure TMyBaseClass.AfterConstruction; override;
begin
MyFunction();
inherited;
end;
这提供了函数本身是纯粹的(不改变对象状态并且不需要太多的对象状态)并且执行成本低廉。
某些优化还可能包括仅针对第一个实例化的一次性测试,并仅将其绑定到调试版本。
TMyBaseClass = class
public function MyFunction : string; virtual; abstract;
public procedure AfterConstruction; override;
private class var MyFunction_tested: Boolean;
end;
....
procedure TMyBaseClass.AfterConstruction; override;
begin
{$IfOpt D+}
if not MyFunction_tested then begin
MyFunction();
MyFunction_tested := true;
end;
{$EndIf}
inherited;
end;
更新。
通过调用" GetClass"获得类。功能。
GetClass('TMyDescendantClass').Create
所以,你使用后期绑定,程序核心(和编译器)实际上并不知道第三方开发人员制作的插件会扩展哪些类。当然,实际上你可能有一个固定的插件列表和一个固定的开发人员列表,但对于没有区别的程序结构。您的程序在编译阶段尚未定义,只有特定的运行时插件集完全定义它。
这意味着在编译核心程序时,你真的无法测试它。任何人在以后任何时候都可以实现一个全新的插件并插入其中。您的Delphi无法看到未来。所以你必须采用运行时检查。这就是基于插件的LEGO类应用程序的工作原理。
问题现在变成了如何安排运行时检查,所以它会像"贪心"尽可能(总是失败=>提前失败:在内部测试阶段,而不是在部署后几个月),同时它将消耗尽可能少的运行时资源。
实际上,检查一个且只有一个特定功能本身似乎有问题。使用RTTI,您可以检查是否仍然存在任何抽象函数。 当然,使用RTTI相对较慢,但我认为从HDD加载BPL文件无论如何都会变慢,所以这里很重要。
我个人认为你应该分开两个不同的操作 - 获取类并实例化它。就像它在Java和DotNet运行时完成一样,在这些操作之间有专用的类检查器/验证器。
TmpClass := GetClass('TMyDescendantClass');
if not TmpClass.InheritsFrom( TMyBaseForm ) then
raise EPluginSystemMisconfiguration.Create(.....);
MyPluginFormClass := TMyBaseForm( TmpClass );
VerifyPluginClass( MyPluginFormClass ); // immediately raises exception if class is not valid
MyPluginForm := MyPluginFormClass.create(Application);
VerifyPluginClass
应该实施哪些检查取决于您。但其中一项检查应该是该类是否具有任何抽象的非实现函数。
请参阅How i can determine if an abstract method is implemented?
如果可能,尝试建立常规单元测试或集成测试的实践 - 那么非常VerifyPluginClass
子程序将在测试框架中重用,为您的共同开发人员提供自己捕获这种错误的方法
答案 2 :(得分:2)
声明包含抽象方法的类不是问题,并且此处未发出警告;如果您创建一个抽象类的实例,并且Delphi编译器发出相应的警告,则会出现问题。例如,
var
Obj: TMyDescendantClass;
begin
Obj:= TMyDescendantClass.Create;
...
导致警告
[DCC警告]:W1020构建'TMyDescendantClass'的实例 包含抽象方法'TMyBaseClass.MyFunction'
答案 3 :(得分:2)
如果将后代类声明为sealed,则编译器会为任何未实现的抽象过程和函数引发错误。因此,以下内容将在编译时引发错误:
TMyBaseClass = class abstract(TForm)
public
function MyFunction : string; virtual; abstract;
end;
TMyDescendantClass = class sealed(TMyBaseClass);
当然,你不能强迫你的同事宣布这个课程是密封的,但也许有帮助。