如何强制Descendant类直接使用Ancestor方法或者有一个新的实现不调用inherited?

时间:2008-11-14 18:47:38

标签: delphi inheritance

我们有TAncestor类,它有一个虚拟方法GetFile 我们将有一些TDescendant = class(TAncestor)可以覆盖GetFile 我们希望确保在这种情况下,这些重写方法不会在其实现中调用inherited 但如果他们没有实现GetFile并且只使用TAncestor中的那个,那就没关系了 有(简单)方法吗?

使其更清晰:
- 是的,医生清楚地说'不要在你的后代班继承使用' - 我无法控制别人在覆盖时会编码什么,也不依赖他们阅读文档 - 我不能将执行限制在确切的类TAncestor中,因为如果他们不提供自己的实现,则后代使用它是合法的 - 我不能把它抽象化,因为它需要有一个默认的实现
- 我希望通过在基类中检测代码是通过后代重写实现来调用的方式来强制执行此操作
- 看堆栈似乎有点矫枉过正,但到目前为止是我的第一个想法

7 个答案:

答案 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不调用其他虚拟方法并且您不关心并发性,这将起作用。