I was writing a bunch of code a few months ago and now I'm adding stuff to it. I realized I wrote a bunch of functions that descend from a class that has about 2/3rds of its functions abstract and the remaining 1/3rd virtual.
I'm pretty much sick of seeing:
function descendent.doSomething() : TList;
begin
inherited;
end;
when I've got this for the base class:
function descendent.doSomething() : TList;
begin
result := nil;
end;
and would hate to wind up with:
function descendent.doSomething() : TList;
begin
end;
and then wonder why something didn't work.
I like using abstract functions because the compiler will let you know if you're liable to get an abstract error because you didn't implement some functions.
My question is, because I'm still a relatively new Delphi programmer and I've never had to maintain anything 8 years hence, is it worth taking the time to prune your code in this manner (i.e. remove functions that just have inherited in them and change your base class functions from abstract to concrete)
It depends on the problem as always. I use interfaces to define the user interface for the set of classes. At least when I know I will have more than one implementation of the underlying actual class. For instance You can have something like this:
IAllInterfaced = interface(IInterface)
procedure ImplementMeEverywhere_1(const Params: TParams);
procedure ImplementMeEverywhere_2(const Params: TParams);
procedure ImplementMeEverywhere_3(const Params: TParams);
end;
TAllInterfaced_ClassA = class(TInterfacedObject, IAllInterfaced)
public
procedure ImplementMeEverywhere_1(const Params: TParams);
procedure ImplementMeEverywhere_2(const Params: TParams);
procedure ImplementMeEverywhere_3(const Params: TParams);
end;
TAllInterfaced_ClassB = class(TInterfacedObject, IAllInterfaced)
public
procedure ImplementMeEverywhere_1(const Params: TParams);
procedure ImplementMeEverywhere_2(const Params: TParams);
procedure ImplementMeEverywhere_3(const Params: TParams);
end;
Here you dont have a common ancestor. Each class only implements the interface and has no common underlying structure in a form of a common base class. This is possible if implementations are so different that they do not share anything, but he interface itself. You still need to use the same interface so you are consistent towards the users of the derived classes.
The second option is:
IAllAbstract = interface(IInterface)
procedure ImplementMeEverywhere_1(const Params: TParams);
procedure ImplementMeEverywhere_2(const Params: TParams);
procedure ImplementMeEverywhere_3(const Params: TParams);
end;
TAllAbstract_Custom = (TInterfacedObject, IAllAbstract)
private
...
public
procedure ImplementMeEverywhere_1(const Params: TParams); virtual; abstract;
procedure ImplementMeEverywhere_2(const Params: TParams); virtual; abstract;
procedure ImplementMeEverywhere_3(const Params: TParams); virtual; abstract;
end;
TAllAbstract_ClassA = class(TAllAbstract_Custom)
public
procedure ImplementMeEverywhere_1(const Params: TParams); override;
procedure ImplementMeEverywhere_2(const Params: TParams); override;
procedure ImplementMeEverywhere_3(const Params: TParams); override;
end;
TAllAbstract_ClassB = class(TAllAbstract_Custom)
public
procedure ImplementMeEverywhere_1(const Params: TParams); override;
procedure ImplementMeEverywhere_2(const Params: TParams); override;
procedure ImplementMeEverywhere_3(const Params: TParams); override;
end;
Here you have a base class for all the classes. In that class you can have common properties or event other classes etc... But all procedures are marked as abstract because they do not perform any common tasks. Abstract ensures the they will be implemented in the derived classes, but you do not need to implement "FieldA" in every class, you only implement it in the "TAllAbstract_Custom". This ensures that the DRY principle is used.
The last option is:
IAllVirtual = interface(IInterface)
procedure ImplementMeEverywhere_1(const Params: TParams);
procedure ImplementMeEverywhere_2(const Params: TParams);
procedure ImplementMeEverywhere_3(const Params: TParams);
end;
TAllVirtual_Custom = (TInterfacedObject, IAllVirtual)
private
...
public
procedure ImplementMeEverywhere_1(const Params: TParams); virtual;
procedure ImplementMeEverywhere_2(const Params: TParams); virtual;
procedure ImplementMeEverywhere_3(const Params: TParams); virtual;
end;
TAllVirtual_ClassA = class(TAllVirtual_Custom)
public
procedure ImplementMeEverywhere_1(const Params: TParams); override;
procedure ImplementMeEverywhere_2(const Params: TParams); override;
procedure ImplementMeEverywhere_3(const Params: TParams); override;
end;
TAllVirtual_ClassB = class(TAllVirtual_Custom)
public
procedure ImplementMeEverywhere_1(const Params: TParams); override;
procedure ImplementMeEverywhere_2(const Params: TParams); override;
procedure ImplementMeEverywhere_3(const Params: TParams); override;
end;
Here all derived classes have a common base virtual procedure. This ensures you do not have to implement every single procedure at the level of the derived classes. You can only override some parts of the code or none at all.
Naturally this are all edge cases, there is room in beetwen them. You can have a mix of those concepts.
Just remember:
Sorry for a long answer but I could not give an easy explanation here because there is none. It all depends on the problem at hand. It is a balance between how much do the derived classes have in common and how different their implementations are.
If the code is really simple and you find it difficult to read and error-prone, then it's probably difficult to read and error-prone. (On the other hand, if the code is complicated and you find it hard to read, it could be your lack of experience. But not something like this.) You'd probably do well to refactor it now, while the issue is still fresh in your mind.
Yes, prune your code.
It makes your other code much easier to read (as you already mentioned, it would be easier to see what methods are actually overwritten). As an added benefit it will be easier to change the signature of the method in the parent class: Imagine you decide to pass one more parameter to a virtual method; You make the change to the parent class, then you'll need to repeat that same change for every child class that inherits from the given parent class. At that point you don't want bogus overwritten methods that just call "inherited"!
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