Delphi:可维护性虚拟与虚拟抽象

发布于 2024-09-25 06:22:18 字数 711 浏览 4 评论 0原文

几个月前我写了一堆代码,现在我正在向其中添加一些东西。我意识到我编写了一堆函数,这些函数源自一个类,该类的函数大约有 2/3 是抽象函数,其余 1/3 是虚拟函数。

我非常厌倦看到:

function descendent.doSomething() : TList;
begin
   inherited;
end;

当我为基类提供了这个:

function descendent.doSomething() : TList;
begin
   result := nil;
end;

并讨厌最终得到:

function descendent.doSomething() : TList;
begin

end;

然后想知道为什么有些东西不起作用。

我喜欢使用抽象函数,因为编译器会告诉您是否因为没有实现某些函数而可能出现抽象错误。

我的问题是,因为我仍然是一个相对较新的 Delphi 程序员,而且 8 年来我从来没有维护过任何东西,是否值得花时间 以这种方式修剪你的代码(即删除刚刚继承的函数并将基类函数从抽象更改为具体)

I was writing a bunch of code a few months ago and now I'm adding stuff to it. I realized I wrote a bunch of functions that descend from a class that has about 2/3rds of its functions abstract and the remaining 1/3rd virtual.

I'm pretty much sick of seeing:

function descendent.doSomething() : TList;
begin
   inherited;
end;

when I've got this for the base class:

function descendent.doSomething() : TList;
begin
   result := nil;
end;

and would hate to wind up with:

function descendent.doSomething() : TList;
begin

end;

and then wonder why something didn't work.

I like using abstract functions because the compiler will let you know if you're liable to get an abstract error because you didn't implement some functions.

My question is, because I'm still a relatively new Delphi programmer and I've never had to maintain anything 8 years hence, is it worth taking the time to prune your code in this manner (i.e. remove functions that just have inherited in them and change your base class functions from abstract to concrete)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

岁月静好 2024-10-02 06:22:18

一如既往,这取决于问题。我使用接口来定义一组类的用户界面。至少当我知道我将拥有多个底层实际类的实现时。例如,您可以有这样的内容:

 IAllInterfaced = interface(IInterface)
    procedure ImplementMeEverywhere_1(const Params: TParams);
    procedure ImplementMeEverywhere_2(const Params: TParams);
    procedure ImplementMeEverywhere_3(const Params: TParams);
  end;

  TAllInterfaced_ClassA = class(TInterfacedObject, IAllInterfaced)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams);
    procedure ImplementMeEverywhere_2(const Params: TParams);
    procedure ImplementMeEverywhere_3(const Params: TParams);
  end;

  TAllInterfaced_ClassB = class(TInterfacedObject, IAllInterfaced)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams);
    procedure ImplementMeEverywhere_2(const Params: TParams);
    procedure ImplementMeEverywhere_3(const Params: TParams);
  end;

在这里您没有共同的祖先。每个类仅实现接口,没有公共基类形式的公共底层结构。如果实现如此不同以至于它们不共享任何东西,但他接口本身,这是可能的。您仍然需要使用相同的接口,以便与派生类的用户保持一致。

第二个选项是:

 IAllAbstract = interface(IInterface)
    procedure ImplementMeEverywhere_1(const Params: TParams);
    procedure ImplementMeEverywhere_2(const Params: TParams);
    procedure ImplementMeEverywhere_3(const Params: TParams);
  end;

  TAllAbstract_Custom = (TInterfacedObject, IAllAbstract)
  private
    ...
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); virtual; abstract;
    procedure ImplementMeEverywhere_2(const Params: TParams); virtual; abstract;
    procedure ImplementMeEverywhere_3(const Params: TParams); virtual; abstract;
  end;

  TAllAbstract_ClassA = class(TAllAbstract_Custom)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); override;
    procedure ImplementMeEverywhere_2(const Params: TParams); override;
    procedure ImplementMeEverywhere_3(const Params: TParams); override;
  end;

  TAllAbstract_ClassB = class(TAllAbstract_Custom)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); override;
    procedure ImplementMeEverywhere_2(const Params: TParams); override;
    procedure ImplementMeEverywhere_3(const Params: TParams); override;
  end;

这里你有一个所有类的基类。在该类中,您可以具有公共属性或事件其他类等...但是所有过程都被标记为抽象,因为它们不执行任何常见任务。 Abstract 确保它们将在派生类中实现,但您不需要在每个类中实现“FieldA”,只需在“TAllAbstract_Custom”中实现它。这确保了 DRY 原则的使用。

最后一个选项是:

 IAllVirtual = interface(IInterface)
    procedure ImplementMeEverywhere_1(const Params: TParams);
    procedure ImplementMeEverywhere_2(const Params: TParams);
    procedure ImplementMeEverywhere_3(const Params: TParams);
  end;

  TAllVirtual_Custom = (TInterfacedObject, IAllVirtual)
  private
    ...
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); virtual;
    procedure ImplementMeEverywhere_2(const Params: TParams); virtual;
    procedure ImplementMeEverywhere_3(const Params: TParams); virtual;
  end;

  TAllVirtual_ClassA = class(TAllVirtual_Custom)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); override;
    procedure ImplementMeEverywhere_2(const Params: TParams); override;
    procedure ImplementMeEverywhere_3(const Params: TParams); override;
  end;

  TAllVirtual_ClassB = class(TAllVirtual_Custom)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); override;
    procedure ImplementMeEverywhere_2(const Params: TParams); override;
    procedure ImplementMeEverywhere_3(const Params: TParams); override;
  end;

这里所有派生类都有一个公共的基本虚拟过程。这确保您不必在派生类级别实现每个过程。您只能覆盖代码的某些部分,或者根本不覆盖。

当然,这都是边缘情况,它们之间还有空间。您可以混合使用这些概念。

请记住:

  1. 接口是强大的工具,可确保您向用户隐藏实现并且拥有共同的使用点(接口)。他们还强制使用一些规范,因为接口需要完整实现。
  2. Abstract 是一个很好的工具,因此您不必在不需要的过程中使用空存根。另一方面,它们迫使您在派生类中实现它们。
  3. 当您拥有每个类都必须实现的公共代码并确保干净的 OP 和 DRY 原则时,虚拟会派上用场。当您拥有并非每个派生类都有或需要的过程时,它们也很受欢迎。

很抱歉回答很长,但我无法在这里给出简单的解释,因为没有。这一切都取决于当前的问题。它是派生类有多少共同点和它们的实现有多少不同之间的平衡。

It depends on the problem as always. I use interfaces to define the user interface for the set of classes. At least when I know I will have more than one implementation of the underlying actual class. For instance You can have something like this:

 IAllInterfaced = interface(IInterface)
    procedure ImplementMeEverywhere_1(const Params: TParams);
    procedure ImplementMeEverywhere_2(const Params: TParams);
    procedure ImplementMeEverywhere_3(const Params: TParams);
  end;

  TAllInterfaced_ClassA = class(TInterfacedObject, IAllInterfaced)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams);
    procedure ImplementMeEverywhere_2(const Params: TParams);
    procedure ImplementMeEverywhere_3(const Params: TParams);
  end;

  TAllInterfaced_ClassB = class(TInterfacedObject, IAllInterfaced)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams);
    procedure ImplementMeEverywhere_2(const Params: TParams);
    procedure ImplementMeEverywhere_3(const Params: TParams);
  end;

Here you dont have a common ancestor. Each class only implements the interface and has no common underlying structure in a form of a common base class. This is possible if implementations are so different that they do not share anything, but he interface itself. You still need to use the same interface so you are consistent towards the users of the derived classes.

The second option is:

 IAllAbstract = interface(IInterface)
    procedure ImplementMeEverywhere_1(const Params: TParams);
    procedure ImplementMeEverywhere_2(const Params: TParams);
    procedure ImplementMeEverywhere_3(const Params: TParams);
  end;

  TAllAbstract_Custom = (TInterfacedObject, IAllAbstract)
  private
    ...
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); virtual; abstract;
    procedure ImplementMeEverywhere_2(const Params: TParams); virtual; abstract;
    procedure ImplementMeEverywhere_3(const Params: TParams); virtual; abstract;
  end;

  TAllAbstract_ClassA = class(TAllAbstract_Custom)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); override;
    procedure ImplementMeEverywhere_2(const Params: TParams); override;
    procedure ImplementMeEverywhere_3(const Params: TParams); override;
  end;

  TAllAbstract_ClassB = class(TAllAbstract_Custom)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); override;
    procedure ImplementMeEverywhere_2(const Params: TParams); override;
    procedure ImplementMeEverywhere_3(const Params: TParams); override;
  end;

Here you have a base class for all the classes. In that class you can have common properties or event other classes etc... But all procedures are marked as abstract because they do not perform any common tasks. Abstract ensures the they will be implemented in the derived classes, but you do not need to implement "FieldA" in every class, you only implement it in the "TAllAbstract_Custom". This ensures that the DRY principle is used.

The last option is:

 IAllVirtual = interface(IInterface)
    procedure ImplementMeEverywhere_1(const Params: TParams);
    procedure ImplementMeEverywhere_2(const Params: TParams);
    procedure ImplementMeEverywhere_3(const Params: TParams);
  end;

  TAllVirtual_Custom = (TInterfacedObject, IAllVirtual)
  private
    ...
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); virtual;
    procedure ImplementMeEverywhere_2(const Params: TParams); virtual;
    procedure ImplementMeEverywhere_3(const Params: TParams); virtual;
  end;

  TAllVirtual_ClassA = class(TAllVirtual_Custom)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); override;
    procedure ImplementMeEverywhere_2(const Params: TParams); override;
    procedure ImplementMeEverywhere_3(const Params: TParams); override;
  end;

  TAllVirtual_ClassB = class(TAllVirtual_Custom)
  public
    procedure ImplementMeEverywhere_1(const Params: TParams); override;
    procedure ImplementMeEverywhere_2(const Params: TParams); override;
    procedure ImplementMeEverywhere_3(const Params: TParams); override;
  end;

Here all derived classes have a common base virtual procedure. This ensures you do not have to implement every single procedure at the level of the derived classes. You can only override some parts of the code or none at all.

Naturally this are all edge cases, there is room in beetwen them. You can have a mix of those concepts.

Just remember:

  1. Interfaces are powerfull tool to ensure that you hide implementation from the user and that you have a common usage point (interface). They also force some norms to be used, because interface needs to be implemented in full.
  2. Abstract is a good tool so you do not have to use empty stubs for procedures where there is no real need for them. On the other side they force you to implement them in derived classes.
  3. Virtual comes in handy when you have common code that must be implemented by every class and that ensures the clean OP and DRY principle. They are also welcome when you have procedures that not every derived class has or needs.

Sorry for a long answer but I could not give an easy explanation here because there is none. It all depends on the problem at hand. It is a balance between how much do the derived classes have in common and how different their implementations are.

夜无邪 2024-10-02 06:22:18

如果代码真的很简单,并且您发现很难阅读并且容易出错,那么它可能就很难阅读并且容易出错。 (另一方面,如果代码复杂并且您发现难以阅读,这可能是您缺乏经验。但不是这样的。)您可能会很好地重构它现在,虽然这个问题在你的脑海中仍然记忆犹新。

If the code is really simple and you find it difficult to read and error-prone, then it's probably difficult to read and error-prone. (On the other hand, if the code is complicated and you find it hard to read, it could be your lack of experience. But not something like this.) You'd probably do well to refactor it now, while the issue is still fresh in your mind.

落墨 2024-10-02 06:22:18

是的,修剪你的代码。

它使您的其他代码更易于阅读(正如您已经提到的,更容易看到哪些方法实际上被覆盖)。作为一项额外的好处,更改父类中方法的签名会更容易:想象一下,您决定再向虚拟方法传递一个参数;您对父类进行更改,然后需要对从给定父类继承的每个子类重复相同的更改。那时,您不希望只调用“继承”的虚假覆盖方法!

Yes, prune your code.

It makes your other code much easier to read (as you already mentioned, it would be easier to see what methods are actually overwritten). As an added benefit it will be easier to change the signature of the method in the parent class: Imagine you decide to pass one more parameter to a virtual method; You make the change to the parent class, then you'll need to repeat that same change for every child class that inherits from the given parent class. At that point you don't want bogus overwritten methods that just call "inherited"!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文