如何强制后代类实现抽象函数?

时间:2016-08-22 09:25:32

标签: delphi inheritance abstract

我有一个基本抽象类,我在其中定义了一个虚拟抽象函数。

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

由于这个事实,实例创建不会引起警告。

4 个答案:

答案 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);

当然,你不能强迫你的同事宣布这个课程是密封的,但也许有帮助。