Interfaces and object-oriented programming接口和面向对象编程
接口最重要的作用就是将类型继承(type inheritance)与类继承(class inheritance)分开。类继承是代码重用的一项有效的工具。派生的类轻松的继承了基类的字段,方法以及属性,并且不需要重新实现公用的方法。在一个强类型的语言中,比如Delphi中,编译器将一个类看作是一种类型,因此类继承与类型继承的概念似乎有些重叠了。但是尽可能地,我们对于类型(type)和类(Class)还是应当严格区分。
许多有关面向对象编程的书籍都将继承关系描述为“是”的关系,比如,一个TSavingsAccount“是”TAccount。你可以体会到相同的含义,当你使用Delphi的Is操作符,来测试一个Account变量是否是TSavingsAccount。
上文例子中的简单的“是”关系已经不能适应要求。正方形属于矩形的一种,但这并不意味着你愿意将TSquare继承自TRectangle。矩形属于多边形的一种,但你可能不希望TRectangle继承自TPolygon。类继承强制派生的类保存基类中声明的所有字段,但这种情况下,派生类并不需要这些信息。一个TSquare对象只需保存它所有边的一个单一长度。然而,一个TRectangle对象却必须保存两个长度。一个TPolygon对象则需要保存许多条边和顶点位置。
解决的方案就是将其从类继承(类C继承了B的字段和方法,而B则继承了A的字段和方法)分离为类型继承(正方形是矩形,矩形又是多边形)。使用接口实现类型继承,则你可以让类继承做它擅长的:字段和方法的继承。
换句话说就是,ISquare继承自IRectangle,而后者又继承自IPolygon。接口遵从了“是”的关系。完全的与接口分离,类TSquare实现了接口ISquare和IRectangle和IPolygon。TRectangle实现了IRectangle和IPolygon。
提示: COM编程的一个约定是将接口的名称命名为I打头的。Delphi的所有接口都遵循了这个约定。注意这只是一个有用的约定,并不是语言的强制要求。
从实现上而言,你可以声明符加的类以达到代码重用的目的。比如,使用TBaseShape实现对所有形状的公用字段和方法。TRectangle继承自TBaseShape然后实现跟根据矩形的特点实现相应方法。多边形依然继承自TBaseShape,并且根据多边形的特点实现相应的方法。
一个画图程序可以操作IPolygon接口来使用各种形状。例子 2-14显示的是基于这种设想的简单的类和接口。注意到每个接口都同时声明了GUID(全局唯一标识符)。使用QueryInterface时GUID是必须的。如果要使用接口的GUID,你可以直接使用接口的名称。Delphi会自动将接口的名称转换为对应的GUID。
例 2-14:分离类型和类继承 type IShape = interface ['{50F6D851-F4EB-11D2-88AC-00104BCAC44B}'] procedure Draw(Canvas: TCanvas); function GetPosition: TPoint; procedure SetPosition(Value: TPoint); property Position: TPoint read GetPosition write SetPosition; end; IPolygon = interface(IShape) ['{50F6D852-F4EB-11D2-88AC-00104BCAC44B}'] function NumVertices: Integer; function NumSides: Integer; function SideLength(Index: Integer): Integer; function Vertex(Index: Integer): TPoint; end; IRectangle = interface(IPolygon) ['{50F6D853-F4EB-11D2-88AC-00104BCAC44B}'] end; ISquare = interface(IRectangle) ['{50F6D854-F4EB-11D2-88AC-00104BCAC44B}'] function Side: Integer; end; TBaseShape = class(TNoRefCount, IShape) private fPosition: TPoint; function GetPosition: TPoint; procedure SetPosition(Value: TPoint); public constructor Create; virtual; procedure Draw(Canvas: TCanvas); virtual; abstract; property Position: TPoint read fPosition write SetPosition; end; TPolygon = class(TBaseShape, IPolygon) private fVertices: array of TPoint; public procedure Draw(Canvas: TCanvas); override; function NumVertices: Integer; function NumSides: Integer; function SideLength(Index: Integer): Integer; function Vertex(Index: Integer): TPoint; end; TRectangle = class(TBaseShape, IPolygon, IRectangle) private fRect: TRect; public procedure Draw(Canvas: TCanvas); override; function NumVertices: Integer; function NumSides: Integer; function SideLength(Index: Integer): Integer; function Vertex(Index: Integer): TPoint; end; TSquare = class(TBaseShape, IPolygon, IRectangle, ISquare) private fSide: Integer; public procedure Draw(Canvas: TCanvas); override; function Side: Integer; function NumVertices: Integer; function NumSides: Integer; function SideLength(Index: Integer): Integer; function Vertex(Index: Integer): TPoint; end;
派生类继承了祖先类实现的接口。TRectangle继承自TBaseShape,则TBaseShape实现了IShape接口也就是TRectangle实现了IShape接口。而接口的继承与此有些不同。接口的继承仅仅为了类型上的便利,也就是说你不必重新再去输入许多方法的声明。当一个类实现一个接口时,并不意味着该类自动的实现了祖先的接口。事实上,该类只实现了出现在该类的声明部分的这些接口(以及在祖先类的声明部分出现的接口)。因此,即使IRectangle继承自IPolygon,TRectangle类还是得将IRectangle和IPolygon显式的罗列出来。
要实现类型体系,你不应当使用引用计数。相反,你需要实现显式的内存管理,如同处理普通的Delphi对象一样。在这种情况下,实现_AddRef和_Release 方法的最好办法就是连根拔除,就象我们在例 2-13里见到的TNoRefCount类那样。还有需要注意的是,不要有任何变量指向失效的引用。一个已经被释放的对象引用可能导致问题,因为Delphi将会自动调用_Release方法。也就是说,永远不要尝试使用指向无效指针的变量,使用接口而不使用引用计数强制你必须这么做。
PartI
PartII
PartIII
PartIV
PartV
PartVI
更多文章
|