实际上我不喜欢将引用计数对象与其他对象混合。我想象,这样做可以成为维护大型项目的噩梦。
但有时我需要从2个或更多的类/接口派生一个类。
你有什么经验?
解决方法
>类继承回答这个问题:“这是什么样的对象?”
>接口实现回答了这个问题:“我该怎么处理这个对象?”
假设你正在建造厨房。 (为以下食物类比提前道歉,我刚刚从午餐回来…)您有三种基本类型的餐具 – 叉子,刀子和勺子。这些都属于器具类别,所以我们将模型化(我省略了一些无聊的东西,如支持字段):
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并没有真正有更多的共同点,你可以放入基类。你可以在技术上用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中,并且派生类简单地拒绝实现没有意义的方法。在设计上,这不是我们想要找到自己陷入困境的一种情况。
当然,真正的问题是,我们使用继承来描述一个对象的作用,而不是它是什么。对于前者,我们有接口。我们可以清理这个设计很多:
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;
我想每个人都得到这个想法。这个点(不是双关语)是我们对整个过程进行了非常细致的控制,我们不必做出任何权衡。我们在这里使用了继承和接口,这些选择不是相互排斥的,只是我们只在抽象类中包含真正的,真正地与所有派生类型相同的功能。
您是否选择使用抽象类或下游的一个或多个接口确实取决于您需要做什么:
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做同样的事情。接口引用计数是一种附带的功能,它是您需要时的有用工具,但是如果要将界面与类语义(通常是您)进行混合,则并不总是使用引用计数功能作为内存管理的一种形式。
事实上,我会说,大多数时候,你可能不想要引用计数语义。是的,在那里,我说过。我总是觉得整个ref-counting的东西只是为了帮助支持OLE自动化(IDispatch)。除非你有一个很好的理由要自动破坏你的界面,只要忘记一下,不要使用TInterfacedObject。您可以随时更改它,当您需要它 – 这是使用界面的要点!从高级设计的角度考虑接口,而不是从内存/终身管理的角度。
所以故事的道德是:
>当需要对象来支持某些特定功能时,请尝试使用界面。>当对象是同一个家族,并且希望它们共享共同的功能时,从一个普通的基类继承。>如果两种情况都适用,那么使用两者!