使用接口(interface)或抽象类分离定义和实现的更好方法是什么?
我实际上不喜欢将引用计数对象与其他对象混合在一起。我想这在维护大型项目时会变成一场噩梦。
但有时我需要从 2 个或更多类/接口(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;
这一切都描述了任何器物共有的数据和功能——它是由什么构成的,它的重量是什么(取决于具体的类型)等等。但是你会注意到抽象类并没有真正做任何事情。一个
TFork
和 TKnife
没有更多的共同点可以放在基类中。您可以在技术上Cut
与 TFork
,但是一个 TSpoon
可能有点牵强,那么如何体现只有某些器物才能做某些事情的事实呢?好吧,我们可以开始扩展层次结构,但它会变得困惑:
type
TSharpUtensil = class
public
procedure Cut(food : TFood); virtual; abstract;
end;
这处理了尖锐的问题,但是如果我们想以这种方式分组呢?
type
TLiftingUtensil = class
public
procedure Lift(food : TFood); virtual; abstract;
end;
TFork
和 TKnife
两者都适合 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;
这是有道理的,因为只有餐具才能进入洗碗机,至少在我们非常有限的厨房里,不包括盘子或杯子等奢侈品。
TSkewer
和 TShovel
可能不会进去,即使他们在技术上可以参与进食过程。另一方面:
type
THungryMan = class
procedure EatChicken(food : TFood; utensil : TUtensil);
end;
这可能不太好。他不能只吃
TKnife
(嗯,不容易)。并且需要一个 TFork
和 TKnife
也没有意义;如果是鸡翅呢?这更有意义:
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),而不是从内存/生命周期管理的角度考虑。所以这个故事的寓意是:
关于delphi - 抽象与接口(interface) - 在 Delphi 中分离定义和实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2279090/