Как заставить класс Descendant либо напрямую использовать метод Ancestor, либо иметь новую реализацию, не вызывающую наследование? - PullRequest
2 голосов
/ 14 ноября 2008

У нас есть класс TAncestor, у которого есть виртуальный метод GetFile.
У нас будет некоторый TDescendant = class (TAncestor), который может переопределить GetFile.
Мы хотим убедиться, что в таком случае эти переопределенные методы не вызывают унаследованные в их реализации.
Но если они не реализуют GetFile и просто используют один из TAncestor, это нормально.
Есть ли (простой) способ сделать это?

чтобы было понятнее:
- да, док четко говорит: «не используйте наследуемый в вашем классе-потомке»
- Я не контролирую, что другие будут кодировать при переопределении, и не полагаюсь на то, что они читают документ
- Я не могу ограничить выполнение точным классом TAncestor, так как потомок может использовать его, если они не предоставляют свою собственную реализацию
- Я не могу сделать это абстрактным, потому что он должен иметь реализацию по умолчанию
- Я хочу применить это в способе определения в базовом классе, что код вызывается с помощью переопределенной реализации потомка
- Глядя на стек, кажется излишним, но моя первая идея пока что

Ответы [ 7 ]

4 голосов
/ 14 ноября 2008

Нет. Невозможно заставить код, находящийся вне вашего контроля, , а не вызывать то, что в противном случае было бы совершенно доступно. Лучшее, что вы можете сделать, - это настоятельно не рекомендовать практику в документации к классу.

Каковы последствия, если потомок вызывает унаследованный метод? Если это значит, что программа перестает работать, то пусть будет так. Программист, который пишет класс-потомок, протестирует код, заметит, что он не работает, а затем сверится с документацией для метода, чтобы убедиться, что он использует его правильно (в этот момент он узнает, что это не так).

Вы могли бы пойти другим путем. Вместо того, чтобы делать функцию виртуальной и иметь потомки, переопределяющие ее, предоставьте свойство-указатель защищенного метода.

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 голос
/ 14 ноября 2008

Если потомки находятся под вашим контролем, просто переопределите метод и не используйте унаследованное ключевое слово 1002 *. Остальное, это не так много, что может быть сделано - это зависит от людей, переопределяющих метод GetFile, чтобы использовать его унаследованный метод или нет. За исключением, может быть, идеи Джейми.

1 голос
/ 14 ноября 2008

Может ли TAncestor.GetFile сокращаться, поэтому его необходимо переопределить, но предоставить вспомогательный метод для людей, которые сами не хотят его реализовывать?

Кроме того, у вас нет контроля над тем, кто отменяет этот метод? например он используется людьми, не входящими в вашу команду?

procedure TDescentdent.GetFile;
begin
  FileUtils.GetFile    
end;

Редактировать: Стив, конечно, прав, если у вас есть контроль над кодом потомка

1 голос
/ 14 ноября 2008

При реализации предка переопределите метод, но не вызывайте унаследованный внутри метода.

procedure TDescentdent.GetFile;
begin
  //do not call inherited
  //Do something new
end;
0 голосов
/ 21 ноября 2008

Конечно, не существует идеального решения, например:

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 не вызывает другие виртуальные методы и вам не нужен параллелизм.

0 голосов
/ 15 ноября 2008

Что ж, уточнение вопроса дало мне решение:

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;
0 голосов
/ 14 ноября 2008

Я не думаю, что вы действительно хотите это сделать, но в любом случае не похоже, что вы правильно используете наследование.

Если вам действительно нужно сделать что-то подобное, возможно, вы могли бы вместо этого использовать шаблон стратегии ? Извлеките GetFile в его собственную иерархию классов с помощью абстрактного класса (скажем, TAbstractFileUtils), который определяет контракт, и конкретных подклассов, которые его реализуют (включая ваш TDefaultFileUtils по умолчанию). Конструктор может взять экземпляр TAbstractFileUtils. Это означает, что вызывающая сторона (или фабричный метод) отвечает за правильную реализацию.

Это не помешает другим создавать подклассы TDefaultFileUtils и вызывать унаследованный GetFile, но в любом случае это не то, как работает наследование.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...