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:
- Interfaces are powerfull tool to ensure that you hide implementation from the user and that you have a common usage point (interface). They also force some norms to be used, because interface needs to be implemented in full.
- Abstract is a good tool so you do not have to use empty stubs for procedures where there is no real need for them. On the other side they force you to implement them in derived classes.
- Virtual comes in handy when you have common code that must be implemented by every class and that ensures the clean OP and DRY principle. They are also welcome when you have procedures that not every derived class has or needs.
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.