Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to force descendant classes to implement an abstract function?

I have a base abstract class in which I have defined a virtual abstract function.

TMyBaseClass = class abstract(TForm)
public
  function MyFunction : string; virtual; abstract;
end;

I have noticed that no error/warning is shown when compiling a not abstract descendant class.

TMyDescendantClass = class(TMyBaseClass);

The goal is that to force my colleagues to implement the function for every descendant class. I found a similar question (Delphi 6: Force compiler error on missing abstract class methods?) but the solution doesn't fit to my goal.

Update1:

Classes are obtained by calling "GetClass" function.

GetClass('TMyDescendantClass').Create

Due to this fact, instances creation doesn't cause warnings.

like image 422
Fabrizio Avatar asked Aug 22 '16 09:08

Fabrizio


People also ask

Can inherited class be abstract?

An abstract class cannot be inherited by structures. It can contain constructors or destructors. It can implement functions with non-Abstract methods. It cannot support multiple inheritances.

Do all subclasses have to implement abstract methods?

The subclass of abstract class in java must implement all the abstract methods unless the subclass is also an abstract class. All the methods in an interface are implicitly abstract unless the interface methods are static or default.

Does abstract class force implementation?

While an abstract class cannot be instantiated, it can have implementation details. The designers of C# chose to force the user to either implement the functionality, or specifically state that the functionality inherited from the interface will not be implemented at this level.

How do you implement an abstract method in a subclass?

To implement features of an abstract class, we inherit subclasses from it and create objects of the subclass. A subclass must override all abstract methods of an abstract class. However, if the subclass is declared abstract, it's not mandatory to override abstract methods.


2 Answers

If you stick to your current design there's nothing you can do beyond waiting for an error at runtime.

If you were referring to the classes statically then you would see warnings if you instantiated classes that contained abstract methods. But because you obtain the classes dynamically, the compiler cannot help you.

Adding an interface to the class wouldn't be much use. Suppose you changed your class so that it implemented an interface? You'd have to implement that in the base class. How would you do so? By using virtual methods? You'd just be back where you started.

Suppose you could somehow force the implementers of derived classes to implement all these abstract methods. What is stopping them from doing so like this:

type
  TMyDescendantClass = class(TMyBaseClass)
  public
    function MyFunction: string; override;
  end;

function TMyDescendantClass.MyFunction: string;
begin
  // TODO: implement this method
end;

Sometimes a contract just cannot be enforced at compile time!


Rather then using abstract methods of classes, you could change tack completely. Don't ask the implementers to derive from a base class. Ask them to supply you with an interface when you request it. That puts the onus on them. They have to implement the interface (and instantiate it). Then they are obliged to implement each function. Not that it stops them from implementing the functions incorrectly of course!

Depending on factors that only you know, that may prove less convenient than your current design. But only you can decide that.

like image 129
David Heffernan Avatar answered Nov 09 '22 05:11

David Heffernan


A somewhat ugly solution would be triggering a runtime exception early on, precluding any use of their descendent classes unless they did the implementation:

 TMyBaseClass = class 
   public function MyFunction : string; virtual; abstract;
   public procedure AfterConstruction; override;
 end;

....

procedure TMyBaseClass.AfterConstruction; override;
begin
  MyFunction();
  inherited;
end;

This is providing that the function itself is pure ( not changing the object state and not requiring much of the object state ) and cheap at execution costs.

Some optimizations might also include one-time testing only for the first instantiation and binding this to debug-builds only.

 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;

UPDATE.

Classes are obtained by calling "GetClass" function. GetClass('TMyDescendantClass').Create

So, you use late binding, where the program core (and compiler) do not actually know which classes they would be extended with by plugins made by third-party developers. Granted, practically you may have a fixed list of plugins and a fixed list of their developers, but for the program structure that makes no difference. You program just is not defined yet during compilation phase, only the specific set of runtime plugins defines it completely.

That means you really can not test it all when compiling your core program. Any person at any later time can implement a brand new plugin and jack it in. Your Delphi just cannot see into the future. So you do have to resort to runtime checking. That is just how plugins-based LEGO-like applications work.

The problem now becomes how to arrange that runtime check, so it would be as "greedy" as possible (failing always => failing early: during in-house testing phase, rather than few months after deployment) and at the same time it would consume as little runtime resources as possible.

Actually, checking for one and only one specific function seems questionable idea per se. Using RTTI you might check if ANY abstract function still remains. Granted, using RTTI is relatively slow, but I think loading BPL files from HDD would be slower anyway, so it matters little here.

Personally I think you should separate two different operations - obtaining the class and instantiating it. Just like it is done in Java and DotNet runtimes, where there are dedicated class checkers/verifiers in-between these operations.

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

What checks should VerifyPluginClass implement is up to you. But one of the checks should be if the class has ANY abstract non-implemented functions.

See How i can determine if an abstract method is implemented?

If possible, try to establish practice of regular unit-testing or integration-testing - then that very VerifyPluginClass subroutine would be reused in the testing framework, providing your co-developers to catch this kind of their errors themselves.

like image 40
Arioch 'The Avatar answered Nov 09 '22 04:11

Arioch 'The