gpt4 book ai didi

delphi - 抽象与接口(interface) - 在 Delphi 中分离定义和实现

转载 作者:行者123 更新时间:2023-12-03 14:34:08 25 4
gpt4 key购买 nike

使用接口(interface)或抽象类分离定义和实现的更好方法是什么?

我实际上不喜欢将引用计数对象与其他对象混合在一起。我想这在维护大型项目时会变成一场噩梦。

但有时我需要从 2 个或更多类/接口(interface)派生一个类。

你的经验是什么?

最佳答案

理解这一点的关键是要意识到它不仅仅是定义与实现。这是关于描述同一个名词的不同方式:

  • 类继承回答问题: “这是什么物体?”
  • 接口(interface)实现回答问题: “我可以用这个物体做什么?”


  • 假设您正在为厨房建模。 (为以下食物类比提前道歉,我刚吃完午饭回来......)你有三种基本类型的器具 - fork 、刀子和勺子。这些都属于器具类别,因此我们将对其进行建模(我省略了一些无聊的东西,例如支持字段):
    type
    TMaterial = (mtPlastic, mtSteel, mtSilver);

    TUtensil = class
    public
    function GetWeight : Integer; virtual; abstract;
    procedure Wash; virtual; // Yes, it's self-cleaning
    published
    property Material : TMaterial read FMaterial write FMaterial;
    end;

    这一切都描述了任何器物共有的数据和功能——它是由什么构成的,它的重量是什么(取决于具体的类型)等等。但是你会注意到抽象类并没有真正做任何事情。一个 TForkTKnife没有更多的共同点可以放在基类中。您可以在技术上 CutTFork ,但是一个 TSpoon可能有点牵强,那么如何体现只有某些器物才能做某些事情的事实呢?

    好吧,我们可以开始扩展层次结构,但它会变得困惑:
    type
    TSharpUtensil = class
    public
    procedure Cut(food : TFood); virtual; abstract;
    end;

    这处理了尖锐的问题,但是如果我们想以这种方式分组呢?
    type
    TLiftingUtensil = class
    public
    procedure Lift(food : TFood); virtual; abstract;
    end;
    TForkTKnife两者都适合 TSharpUtensil ,但是 TKnife举起一块鸡肉是非常糟糕的。我们最终要么不得不选择这些层次结构中的一个,要么只是将所有这些功能都推到通用 TUtensil 中。并且派生类只是拒绝实现没有意义的方法。在设计方面,我们不想陷入困境。

    当然,真正的问题在于我们使用继承来描述对象的作用,而不是它是什么。对于前者,我们有接口(interface)。我们可以对这个设计进行很多清理:
    type
    IPointy = interface
    procedure Pierce(food : TFood);
    end;

    IScoop = interface
    procedure Scoop(food : TFood);
    end;

    现在我们可以理清具体类型的作用:
    type
    TFork = class(TUtensil, IPointy, IScoop)
    ...
    end;

    TKnife = class(TUtensil, IPointy)
    ...
    end;

    TSpoon = class(TUtensil, IScoop)
    ...
    end;

    TSkewer = class(TStick, IPointy)
    ...
    end;

    TShovel = class(TGardenTool, IScoop)
    ...
    end;

    我想每个人都明白这一点。重点(不是双关语)是我们对整个过程有非常细粒度的控制,我们不必做任何权衡。我们在这里同时使用继承和接口(interface),这些选择并不是相互排斥的,只是我们只在抽象类中包含了所有派生类型真正非常通用的功能。

    您是否选择使用抽象类或下游的一个或多个接口(interface)实际上取决于您需要用它做什么:
    type
    TDishwasher = class
    procedure Wash(utensils : Array of TUtensil);
    end;

    这是有道理的,因为只有餐具才能进入洗碗机,至少在我们非常有限的厨房里,不包括盘子或杯子等奢侈品。 TSkewerTShovel可能不会进去,即使他们在技术上可以参与进食过程。

    另一方面:
    type
    THungryMan = class
    procedure EatChicken(food : TFood; utensil : TUtensil);
    end;

    这可能不太好。他不能只吃 TKnife (嗯,不容易)。并且需要一个 TForkTKnife也没有意义;如果是鸡翅呢?

    这更有意义:
    type
    THungryMan = class
    procedure EatPudding(food : TFood; scoop : IScoop);
    end;

    现在我们可以给他 TFork , TSpoon , 或 TShovel ,他很高兴,但不是 TKnife ,这仍然是一个器物,但在这里并没有真正的帮助。

    您还会注意到,第二个版本对类层次结构的更改不太敏感。如果我们决定改变 TFork继承自 TWeapon相反,只要它仍然执行 IScoop 我们的人仍然很高兴.

    我也在这里掩盖了引用计数问题,我认为@Deltics 说得最好;只是因为你有那个 AddRef并不意味着你需要用它做同样的事情 TInterfacedObject做。接口(interface)引用计数是一种附带功能,在您需要时它是一个有用的工具,但是如果您打算将接口(interface)与类语义混合(而且经常是这样),它并不总是使使用引用计数功能作为内存管理的一种形式。

    事实上,我什至要说大多数时候,您可能不想要引用计数语义。是的,有,我说过。我一直觉得整个引用计数只是为了帮助支持 OLE 自动化等( IDispatch)。除非你有充分的理由想要自动销毁你的界面,否则就别管它了,不要使用 TInterfacedObject根本。您可以在需要时随时更改它 - 这就是使用界面的意义所在!从高级设计的角度考虑接口(interface),而不是从内存/生命周期管理的角度考虑。

    所以这个故事的寓意是:
  • 当您需要一个对象支持某些特定功能时,请尝试使用接口(interface)。
  • 当对象属于同一家族并且您希望它们共享共同特征时,请从共同基类继承。
  • 如果两种情况都适用,那么两者都使用!
  • 关于delphi - 抽象与接口(interface) - 在 Delphi 中分离定义和实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2279090/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com