代码之家  ›  专栏  ›  技术社区  ›  Francesca

如何强制子类直接使用祖先方法,或者让新的实现不调用继承的?

  •  2
  • Francesca  · 技术社区  · 17 年前

    我们有tancestor类,它有一个虚拟方法getfile。
    我们将有一些tdescentant=class(tancestor),它可以覆盖getfile。
    我们希望确保在这种情况下,这些重写的方法不会在其实现中调用继承的方法。
    但如果他们不实现getfile,只使用来自tancestor的,那就没问题了。
    有没有一种(简单的)方法可以做到这一点?

    为了更清楚:
    -是的,doc清楚地说“不要在您的后代类中使用继承的”
    -我无法控制重写时其他人将编写什么代码,也不依赖于他们阅读文档
    -我不能将执行限制到确切的类tancestor,因为如果后代不提供自己的实现,那么使用它是合法的。
    -我不能把它抽象化,因为它需要一个默认的实现
    -我想通过在基类中检测代码是通过子代重写的实现调用的方法来实现这一点
    -看着这堆看起来有点过头了,但这是我到目前为止的第一个想法

    7 回复  |  直到 17 年前
        1
  •  4
  •   Rob Kennedy    17 年前

    不,没有办法强迫你控制之外的代码 调用其他完全可访问的内容。你所能做的最好的就是强烈反对在课堂上的文档练习。

    如果后代调用继承的方法,会产生什么后果?如果这意味着程序停止工作,那就这样吧。编写子类的程序员将测试代码,注意它不起作用,然后查阅该方法的文档以确保他正确地使用了它(此时他将了解到他没有使用)。

    你可以采取另一种方法。提供一个受保护的方法指针属性,而不是使函数成为虚拟函数,并让子体覆盖它。

    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 匹配任何签名 获取文件 将。

        2
  •  1
  •   Steve    17 年前

    实现祖先时,重写方法,但不要调用方法内部继承的。

    procedure TDescentdent.GetFile;
    begin
      //do not call inherited
      //Do something new
    end;
    
        3
  •  1
  •   Fabricio Araujo    17 年前

    如果子体在您的控制下,只需重写方法 不使用 继承 关键字。否则,那就不算什么了。 完成-由重写getfile方法的人来使用 是否继承方法。除了,也许,杰米的主意。

        4
  •  1
  •   Jamie    17 年前

    可以将tancestor.getfile设为abtract,因此它必须被重写,但是为那些不想自己实现它的人提供一个帮助方法吗?

    另外,您是否无法控制谁正在重写此方法?你的团队外部人员是否使用它?

    procedure TDescentdent.GetFile;
    begin
      FileUtils.GetFile    
    end;
    

    编辑:如果你能控制子代代码,史蒂夫当然是对的。

        5
  •  0
  •   Gabe Moothart    17 年前

    我不认为你真的想这么做,但无论如何,听起来你并没有正确地使用继承。

    如果你真的需要这样做,也许你可以用 strategy pattern 相反?将getfile解压到它自己的类层次结构中,使用定义协定的抽象类(比如tabstractfileutils)和实现协定的具体子类(包括默认的tdefaultfileutils)。构造函数可以获取tabstractfileutils的实例。这意味着调用方(或工厂方法)负责提供正确的实现。

    这不会阻止其他人对tdefaultfileutils进行子类化并调用继承的getfile,但这并不是继承的工作方式。

        6
  •  0
  •   Francesca    17 年前

    好吧,必须澄清这个问题给了我一个解决方案:

    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;
    
        7
  •  0
  •   Kcats    17 年前

    当然,没有完美的解决方案,例如:

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