我们有TAncestor类,它有一个虚拟方法GetFile 我们将有一些TDescendant = class(TAncestor)可以覆盖GetFile 我们希望确保在这种情况下,这些重写方法不会在其实现中调用inherited 但如果他们没有实现GetFile并且只使用TAncestor中的那个,那就没关系了 有(简单)方法吗?
使其更清晰:
- 是的,医生清楚地说'不要在你的后代班继承使用'
- 我无法控制别人在覆盖时会编码什么,也不依赖他们阅读文档
- 我不能将执行限制在确切的类TAncestor中,因为如果他们不提供自己的实现,则后代使用它是合法的
- 我不能把它抽象化,因为它需要有一个默认的实现
- 我希望通过在基类中检测代码是通过后代重写实现来调用的方式来强制执行此操作
- 看堆栈似乎有点矫枉过正,但到目前为止是我的第一个想法
答案 0 :(得分:4)
没有。没有办法强制控制之外的代码不调用一些完全可访问的东西。你能做的最好是强烈反对课堂文档中的练习。
如果后代调用继承的方法会有什么后果?如果这意味着程序停止工作,那就这样吧。编写后代类的程序员将测试代码,注意它不起作用,然后查阅方法的文档以确保他正确使用它(此时他将学习他不是)。< / p>
你可以采取另一种方法。不是将函数设置为虚拟,而是让后代覆盖它,而是提供受保护的方法指针属性。
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;
基类提供了一个方法指针,后代可以指定它们以指示它们需要自己的特殊处理。如果后代为该属性提供值,则基类的GetFile
方法将使用它。否则,它将使用标准实现。定义TGetFileImpl
以匹配GetFile
的签名。
答案 1 :(得分:1)
实现祖先时,重写方法,但不要在方法内部调用inherited。
procedure TDescentdent.GetFile;
begin
//do not call inherited
//Do something new
end;
答案 2 :(得分:1)
可以使TAncestor.GetFile abtract所以必须重写它,但为那些不想自己实现它的人提供帮助方法吗?
另外,您是否无法控制谁覆盖此方法?例如你的团队外部人员使用它吗?
procedure TDescentdent.GetFile;
begin
FileUtils.GetFile
end;
编辑:如果您可以控制后代代码,那么史蒂夫当然是正确的
答案 3 :(得分:1)
如果后代在您的控制之下,只需覆盖该方法即可 并且不使用继承的关键字。否则,那可能不是很多 完成 - 这取决于覆盖GetFile方法的人使用它 继承方法与否。也许,杰米的想法除外。
答案 4 :(得分:0)
我认为你真的不想这样做,但无论如何它听起来并不像你正确使用继承。
如果你真的需要这样做,也许你可以使用strategy pattern代替?将GetFile提取到自己的类层次结构中,使用定义契约的抽象类(比如TAbstractFileUtils)和实现它的具体子类(包括默认的TDefaultFileUtils)。构造函数可以使用TAbstractFileUtils的实例。这意味着调用者(或工厂方法)负责提供正确的实现。
这不会阻止其他人继承TDefaultFileUtils并调用继承的GetFile,但这不是继承如何工作。
答案 5 :(得分:0)
嗯,不得不澄清这个问题给了我一个解决方案:
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;
答案 6 :(得分:0)
当然,没有完美的解决方案,例如:
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;
如果GetFile不调用其他虚拟方法并且您不关心并发性,这将起作用。