Reputation: 21640
We have the TAncestor class which has a virtual method GetFile.
We will have some TDescendant = class(TAncestor) which may override GetFile.
We want to insure that in such a case those overriden methods do not call inherited in their implementation.
But if they don't implement GetFile and just use the one from TAncestor, it's fine.
Is there a (simple) way to do this?
to make it clearer:
- yes, doc clearly says 'do not use inherited in your descendant class'
- I have no control of what others will code when overriding and don't rely on them reading the doc
- I cannot restrict the execution to the exact class TAncestor as it is legit for descendant to use it if they don't provide their own implementation
- I cannot make it abstract because it needs to have a default implementation
- I want to enforce this with a way of detecting in the base class that the code is called through a descendant overridden implementation
- Looking at the stack seems overkill but is my 1st idea so far
Upvotes: 2
Views: 2271
Reputation: 884
Of course, no solution is perfect, for example:
procedure TBadDescendant.GetFile;
var
AncestorImpl : procedure(This : TObject);
ThisClass : TClass;
begin
AncestorImpl := @TAncestor.GetFile;
ThisClass := ClassType;
PPointer(Self)^ := TAncestor;
AncestorImpl(Self);
PPointer(Self)^ := ThisClass;
ShowMessage('TBadDescendant code for GetFile');
end;
This will work if GetFile does not call other virtual methods and you don't care about concurrency.
Upvotes: 0
Reputation: 21640
Well, having to clarify the question gave me a solution:
type
TForm1 = class(TForm)
btnGoodDescendant: TButton;
btnBadDescendant: TButton;
btnSimpleDescendant: TButton;
procedure btnGoodDescendantClick(Sender: TObject);
procedure btnBadDescendantClick(Sender: TObject);
procedure btnSimpleDescendantClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TAncestor = class
public
procedure GetFile; virtual;
end;
TBadDescendant = class(TAncestor)
public
procedure GetFile; override;
end;
TGoodDescendant = class(TAncestor)
public
procedure GetFile; override;
end;
TDescendant = class(TAncestor)
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TAncestor.GetFile;
type
TGetFileImpl = procedure of object;
var
BaseGetFile, GetFileImpl: TGetFileImpl;
ClassAncestor: TClass;
begin
// detecting call through inherited...
GetFileImpl := GetFile; // method actually called
ClassAncestor := ClassType;
while (ClassAncestor <> nil) and (ClassAncestor <> TAncestor) do
ClassAncestor := ClassAncestor.ClassParent;
if ClassAncestor = nil then
raise Exception.Create('no ancestor???');
BaseGetFile := TAncestor(@ClassAncestor).GetFile; // TAncestor code
// if we are here, we should be directly using TAncestor code, not
// not calling inherited from a derived class
// thus the actual code should be exactly TAncestor code.
if TMethod(GetFileImpl).Code <> TMethod(BaseGetFile).Code then
raise Exception.Create('You must not call inherited!');
// this is the Ancestor work code here
ShowMessage('Ancestor code for GetFile');
end;
{ TBadDescendant }
procedure TBadDescendant.GetFile;
begin
inherited;
ShowMessage('TBadDescendant code for GetFile');
end;
{ TGoodDescendant }
procedure TGoodDescendant.GetFile;
begin
ShowMessage('TGoodDescendant code for GetFile');
end;
procedure TForm1.btnGoodDescendantClick(Sender: TObject);
begin
with TGoodDescendant.Create do
GetFile;
end;
procedure TForm1.btnBadDescendantClick(Sender: TObject);
begin
with TBadDescendant.Create do
GetFile;
end;
procedure TForm1.btnSimpleDescendantClick(Sender: TObject);
begin
with TDescendant.Create do
GetFile;
end;
Upvotes: 0
Reputation: 163257
No. There is no way to force code outside your control to not call something that is otherwise perfectly accessible. The best you can do is strongly discourage the practice in the documentation for the class.
What are the consequences if a descendant calls the inherited method? If it means the program stops working, then so be it. The programmer who writes the descendant class will test the code, notice that it doesn't work, and then consult the documentation for the method to ensure that he's using it correctly (at which time he'll learn he isn't).
You could take another approach. Instead of making the function virtual, and having descendants override it, provide a protected method-pointer property.
type
TGetFileImpl = procedure of object;
TAncestor = class
private
FGetFile: TGetFileImpl;
protected
property GetFileImpl: TGetFileImpl write FGetFile write FGetFile;
public
procedure GetFile; // not virtual.
end;
TDescendant = class(TAncestor)
private
procedure SpecializedGetFile;
public
constructor Create;
end;
procedure TAncestor.GetFile;
begin
if Assigned(GetFileImpl) then
GetFileImpl
else begin
// Do default implementation instead
end;
end;
constructor TDescendant.Create;
begin
GetFileImpl := SpecializedGetFile;
end;
The base class provides a method pointer that descendants can assign to indicate they want their own special handling. If the descendant provides a value for that property, then the base class's GetFile
method will use it. Otherwise, it will use the standard implementation. Define TGetFileImpl
to match whatever the signature of GetFile
will be.
Upvotes: 4
Reputation: 3172
Could make TAncestor.GetFile abtract so it has to be overriden but provide a helper method for people who don't want to implement it themselves?
Also, do you not have control over who is overriding this method? e.g. is it used by people external to your team?
procedure TDescentdent.GetFile;
begin
FileUtils.GetFile
end;
Edit: Steve is of course right if you have control over the descendant code
Upvotes: 1
Reputation: 3820
If the descendants are under your control, just override the method and not use the inherited keyword. Else, that's not much that can be done - it's up to the people overriding the GetFile method to use it's inherited method or not. Except, maybe, the Jamie's idea.
Upvotes: 1
Reputation: 32072
I do not think you really want to do this, but in any case it doesn't sound like you're using inheritance properly.
If you really need to do something like this, maybe you could use the strategy pattern instead? Extract GetFile into its own class hierarchy, with an abstract class (say, TAbstractFileUtils) that defines the contract, and concrete subclasses that implement it (including your default TDefaultFileUtils). The constructor could take an instance of TAbstractFileUtils. This means the caller (or a factory method) is responsible for providing the correct implementation.
This won't prevent others from subclassing TDefaultFileUtils and calling the inherited GetFile, but that is just not how inheritance works anyway.
Upvotes: 0
Reputation: 6460
When you implement the ancestor, override the method, but don't call inherited inside the method.
procedure TDescentdent.GetFile;
begin
//do not call inherited
//Do something new
end;
Upvotes: 1