The wind call my name

用知识和思考来丈量世界
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

《Programming .Net Components》学习笔记(六)

Posted on 2007-05-07 22:11  徐鸿翼  阅读(489)  评论(0编辑  收藏  举报

一、接口分解

一个接口是一组有逻辑关系的方法和属性集合。所谓“逻辑关系”通常是特定的域。可以认为接口是同一实体的不同侧面。一旦确定了实体支持的操作和属性,就需要把他们分配到接口中去,这称为“interface factoring(接口分解)”。在面向组件应用程序中,接口是可重用的基本单位。那么根据系统中其他实体分解的接口是否可以重用?什么样的实体侧面能被逻辑分解为接口并被其他实体使用?


假设现在要模拟一只狗。要求狗可以叫,接收指令还有一个兽医诊所注册号码和是一个是否接种过疫苗的属性。现在,可以定义IDog接口并拥有不同种类的狗了,例如PoodleGermanShepherd,都实现了IDog接口:

    public interface IDog

    {

        void Fetch();

        void Bark();

        long VetClinicNumber { get;set;}

        bool HasShots { get;set;}

    }

    public class Poodle : IDog

    {...}

    public class GermanShepherd:IDog

    {...}

但是,像IDog接口这样的组合不是分解良好的。即使所有的接口成员都是一只狗所应具备的特性,但FetchBark相对于VetClinicNumberHasShots有更强的逻辑关系。Fetch()和Bark()专注于狗作为一个生命体的侧面,即动作实体,而VetClinictNumberHasShots专注于另一个侧面,涉及到在兽医诊所的宠物报告。比较好的方法是把VetClinicNumberHasShots提取出来分解成一个独立的接口IPet

    public interface IPet

    {

        long VetClinicNumber { get;set;}

        bool HasShots { get;set;}

    }

    public interface IDog

    {

        void Fetch();

        void Bark();

    }

因为作为宠物的侧面是独立于犬类侧面的,其他实体(如猫)也可以重用IPet接口:

    public interface IPet

    {

        long VetClinicNumber { get;set;}

        bool HasShots { get;set;}

    }

    public interface IDog

    {

        void Fetch();

        void Bark();

    }

    public interface ICat

    {

        void Purr();

        void CatchMouse();

    }

    public class Poodle:IDog,IPet

    {...}

    public class Siamese:ICat,IPet

{...}

这样的分解,反过来讲就可以把诊所管理方面的应用和具体的宠物类型解耦。当在方法之间存在若逻辑关系的时候常常就需要把某些操作和属性提取分解到一个新接口中去。但是,同一操作有时会出现在几个不相关联的接口中,并且与各自的接口逻辑关联。例如,猫和狗都需要脱毛和哺育后代。逻辑上,脱毛既是狗的一个操作,同时又是猫的一个操作。在这种情况下,就可以分解接口到接口层次中来取代分离的接口:

    public interface IMammal

    {

        void ShedFur();

        void Lactate();

    }

    public interface IDog : IMammal

    {

        void Fetch();

        void Bark();

    }

    public interface ICat : IMammal

    {

        void Purr();

        void CatchMouse();

}


二、分解尺度

正确的接口分解的结果是更专门的,松耦合,优化的和和可重用的接口,随后这些优点会同样适用到系统当中去。总之,接口分解的结果是更少成员的接口。当设计基于组件的系统时,需要平衡两个相冲突的力量,如下图:

 

如果有太多的细粒度接口,将会很容易的实现每一个接口,但整合所有这些接口的总开销会十分巨大的。另一方面,如果只有少量的复杂的巨大的分解不好的接口,那么这些接口实现的开销会是十分巨大的,即使整合这些接口的成本或许比较低。在任何给定的系统中,涉及设计和维护实现接口的组件的总工作量,是以上两种分解的总和。如上图所示,关系到接口的尺寸和数量,是存在一个最小开销或工作量区域的。接口的分解问题是门独立的组件技术接下来是作者的经验分享,并给出了一些指导规则: 


1.只包含一个成员的接口是可能的,但应避免这样。一个接口是一个实体的一个侧面,如果你能用一个方法或属性来表达它,那么这个侧面一定是十分呆滞的(@_@)。检查这个孤立的方法:是否使用了太多的参数?是否是意义含糊不清的,应该把它分解成多个方法?或者把这个方法或属性分解到一个已存在的接口中去。

 

2.接口成员的最佳数量是3-5个。如果更多,如6-9个,仍可相对良好地处理,但是,这时应该试着观察成员,试着合并某些方法,来防止成员数量超出合理的范围。如果一个接口有12个或更多的方法,那么就应该明确地把它们分解到分离的接口或接口层次中去。代码标准中应当设置一些上限并保证永远不会超出,无论在什么情况下。

 

3.另一个规则是接口成员中方法和属性的比率。接口允许客户端调用抽象的操作,而不用关心具体的实现细节。属性被认为是“just-enough-encapsulation(刚好封装)”的。以属性的方式暴露成员变量要比直接访问要好,因为这样就可以封装对象中变量设置和读取的业务逻辑,防止蔓延到客户端。理想情况下,应当让客户端懒得搭理属性,客户端只管调用方法,而应当让对象关心它自身的状态。因而,接口中方法应多于属性,至少21.。一个特殊情况是,接口只定义属性而没有方法(如上文中的IPet)。

 

4.如果可能的话,最好避免在接口中定义事件。让对象来决定他是否需要一个事件成员变量。

 

关于分解接口的忠告:以上规则及使用泛型的尺度都只能作为工具,来帮助衡量和评估具体的设计,没有技术和经验的替代品,总是要具体问题具体分析,应用判断力,审视自己鉴于这些尺度都做了什么。


根据原版英文翻译总结的,所以不足和错误之处请大家不吝指正,谢谢:)