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.
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.
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With