慎用继承

1.类继承在继承实现的同时也继承接口,因此,如果主要目的是继承实现,那么采用继承就是不当之举


2.JDK的开发者也会出现问题

java.util.Properties类,设计者选择让他继承HashTable类。表面上这种设计不算离谱。properties比hashtable多了两个要求

可持久化和可人工读写。对于前者,只须增加一些诸如store.load的接口即可。但对于后者,则要求键值均为String,这意味

properties类型的put方法中的先验条件比其超类强,从而违背里氏原则

   JDK中的注释也声明,强烈反对put和put ALl,建议用 setProperty的方法,否则在调用store方法会失败

  违反规范抽象


3.就类的设计者而言,依赖稳定来禁止某个接口实为下策,暴露了设计上的缺陷。就类的使用者而言,应该认真阅读规范文档,

避免错误的用法而导致错误的程序


4.继承还能通过什么方法破坏封装

<1>采用proredted域成员本身是不值得提倡的;另一方面,既然采用,就应当是特意为子类提供

其一,需要封装的信息不只是内部数据,还包括实现方式和内部逻辑。当一个类通过类继承成为另一个类的子类时,本身就暴露

了部分实现方式,从某种意义上说是一种不完整的封装

其二,子类除了能与父类亲密接触外,还有一个特权,那就是覆盖父类的多态方法,这种权利很大,也很微妙。对恶意者,它是一个

安全漏洞。


5public  class Stack{

   private Vector data = new Vector();


   public void push(E item){

      data.addElement(item);

  }


   public void multiPush(E..items){

       for(E item : items){

      push(item);

     }

   }

}

假如我们需要一种特殊的堆栈,能记录先后压栈的次数


6.拜继承所赐,子类能获得重新定义父类方法的能力,在有意识地利用这种能力去进攻的同时。千万要有意思的去防守。由于子类与父类代码

上是隔离的,多态机制有是隐性的,防守起来特为不易,第一个防守的要求:子类应该坚持父类的外在行为,不能破坏父类指定的服务规范。

理论上人们容易理解里氏代换原则的重要性,但实践上经常会有疏漏。在覆盖父类的某一个方法时注意该方法的规范。却无意破坏了相关方法

的规范。


第二个防守要点:子类应该正视父类的内在逻辑,即不能忽视或破坏父类规范之内的逻辑关联,有不能假设或依赖规范之外的逻辑关联


CountingStack类的最初版本之所以出错。正是由于忽视父类的push和multipush之间的逻辑关系。注意到并利用这种非规范的关联。他的修改

是为方法删除子类的一个覆盖。但是这种依赖实现非非接口的一个覆盖没有任何保障。我的修改方法是保持子类不变,让父类mulpush不在依赖

push.在没有相应的规范声明情况下,同样治标不治本,子类调用父类的方法,父类通过多态有调用子类的方法,当这种关系导致父类不能为了子类提供

独立与其实现细节的服务时,父类的数据抽象在子类勉强就失效。


7如果子类知趣地回避覆盖,仅仅增加一些新的方法,的确安全性很好,但这谈不上高层无忧


8.有必要提醒一下,我们主要针对的是实现继承,至于接口继承,只要接口是标准的,稳定的,一般是提倡的


9.提倡接口接触,慎用实现继承。实现继承最大的不好是在类与类建立了强的耦合关系,是代码趋于僵化,这种关联是永固的,

以建立,无法解决。从此,该类无法超出此家庭圈。除非毫无顾忌自己的客户。该类不仅要了解其父类,还要了解protected成员,最麻烦

的是,如果覆盖某一个方法,还需要了解父类之间的内部逻辑。如果想增加一个方法,还不能和父类版本冲突。


10 实现继承至少有3出用武之地

希望访问基础类的protected成员

希望覆盖基类的方法,并且难以用合成变通

希望成为基础类的子类

更实际的建议是:每当为继承与否不定时,问自己两个问题,采用合成是否会遇到无法克服的困难。基础类是否专为设计而设计


12理想的继承树应该是:所有叶子都是具体类,而所有的树枝都是抽象类。

在实际中当然不可能完全做到,但是应尽可能向此目标靠拢。允许一个类被继承,意味着其服务对象除了普通客户以外,有

增加了一类特殊的客户--继承者


13Stack类中最让人困惑的是push和mulpush之间的依赖关系,前者可以依赖后者,后者可能依赖前者,二者也可能相互依赖。

这本属于实现细节,但为子类设计,应明确地规范化,一点成为规范,子类可据此选择正确的覆盖


14规范抽象的作用就是讲代码的功能从实现中分离出来,如今把实现细节写入规范文档,不是自相矛盾吗?很遗憾,这就是

继承的代价,JDK,API中有很多例子


15除了在规范上下工夫还可以在设计上下工夫,实现语义和语法的双重保护


16可覆盖的方法具有扩展性,但也是破坏封装性的,相反,不可覆盖的方法虽然丧失了部分的灵活性,但同时具备完整性

和稳定顶和可靠性


17.一个方法,如果是共有的,就不要多态

  如果是多态的,就不要公有。


18在Stack类的该进中,他的push方法实际有两个功能,一方面,他为外界提供服务,属于公开接口

另一方面,它为自身提供服务,被multipush 调用,属于局部实现,尽管这是很常见的做法,但一人有两种角色不值得

推荐,因为这两种角色可能有冲突,事实上,一般后者的角色是有私有方法完成。如果仅仅在Stack类本身,问题不大,比较他的

接口与实现是作为一个整体来维护的。

问题在于push是多态的,有可能被覆盖,而替换只需要满足里氏原则,即保证父类接口的规范,却没有实现上面的要求,换言之,

子类的push方法,没有同时胜任两个角色的功能。一种解决方法是在规范中详细注明push方法在内部实现中所起的作用,但缺点

是加重子类理解和实现规范的负担,也一定程度桎梏了父类的实现自由。


另一种似乎从矛盾的根源入手,将push和内外职责能进行分解,对内的部分多态而不共公开。对外的部分公开而不多态。。


19软件设计的4字要诀:外静内动

外静指保持外部的接口不变,内动值允许内部实现变动,大道库,框架,架构设计,小到具体函数,类等实现,都有。在规范抽象中

静的是功能规范,东的是实现细节;在数据抽象中,静的是API接口,动的是接口实现,在多态抽象中,静的是interface接口,动的是

实现类;在非虚接口中,静的是非多态的对外接口,东的是多态对你的挂钩。


20对于dopush(E item)中增加protedted beforePush,afterPush,doPush。虽然解决脆弱基类的问题,但是还要有问题,可以想见。r

如果3个多态方法beforePush,doPush,afaterPush之间存在任何依赖关系而规范中有没有说明,我们可能会重复之前的问题


21寻根溯源,一切纠葛的起因是类的共有方法直接或间接的调用自身的另外一个多态方法。如果这种自用只是为一时之用,则应该

尽量避免。如果二者的确存在必然的联系。则类的维护者有责任将此联系规范化,以便指导子类正确地覆盖被调用方法。同时尽量让

共有的调用方法是非多态的,让多态的调用时非公有的,以便分离杰克和挂钩的责任,保证代码的健壮性,并减轻子类的实现。

如此规范和设计双管齐下,一个类才算的上是合格的父类。


22.继承的层次不易过深,在构成方法中不宜或 间接调用多态方法。


23.继承时一种静态,显现关系

合成是一种动态,隐形关系


24子类在覆盖父类某一方法时,不仅要保证该方法的规范,还要维护相应的关联方法的规范;即不能忽视或破坏父类规范之内的逻辑关系,有不能

假设或依赖规范之外的逻辑关系

你可能感兴趣的:(java与模式(OOD))