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

标签 delphi oop interface abstract-base-class

使用接口(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/

    相关文章:

    java - 当变量不为空时获取变量的值

    oop - Golang 在具有私有(private)访问权限的结构中嵌入接口(interface)

    java - Eclipse 可以自动生成第三方库类的接口(interface)吗?

    c# - 建模(和映射)具有两个多态性的类层次结构?

    delphi - 改变 TComboBox 的 ITEMINDEX 不会触发它的 OnChange 事件

    delphi - 如何优雅地退出在 Delphi 中执行代码的 MDI 表单

    delphi - 如何让 markdown.js 在 Delphi 的 TWebBrowser 中将 Markdown 文档显示为 HTML?

    oop - 您如何命名接口(interface)的 "reference"实现?

    c# - 功能灵活的 super 类型

    delphi - 如何实现两个具有相同名称方法的接口(interface)?