现在的位置: 首页 > 综合 > 正文

读《Effective java 中文版》(15)

2012年12月22日 ⁄ 综合 ⁄ 共 1693字 ⁄ 字号 评论关闭

读《Effective java 中文版》(15)

第14条:复合优先于继承

  继承是实现代码重用的有力手段,但不适当地使用继承会导致脆弱的软件。

  • 在一个包内使用继承是非常安全的,因在那儿子类和超类在同一个程序员的控制之下。
  • 对于专门为了继承而设计、且有很好文档说明的类,使用继承也是安全的。
  • 对普通的具体类(concret class)时行跨跃包边界的继承,则是非常危险的。

  与方法调用不同的是,继承打破了封装性,一个子类依赖于其超类中特定功能的实现细节。超类的实现的变化,则子类可能会被打破。

  • 自用性,即超类中不同公有方法的实现时存在调用关系,当子类改写这些方法时,常会导致子类的脆弱
  • 超类在它的后续方法中增加了新的方法,如果子类不能及时改写这些方法,异常的数据或操作出现。
  • 子类继承超类后,增加了一个新的方法,则当超类在新版中也增加了具有相同原型特征的方法时,可能会出现问题

  有一种办法可以避免上述所有问题:新类,不是扩展一个已有类,而是设置一个私有域,它引用这个已有类的一个实例。这种设计被称为“复合(composition)”。新类中的每个实例方法,都可以调用被包含的已有类实例中对应的方法,并返回它的结果,即为“转发方法(forwarding method)”。这样的类比较稳固,这不依赖于已有类的实现细节。一个类的实例都把另一个类的实现包装起来了,则前者的类叫做包装类(wrapper class)。

  看一个例子:

//wrapper class - uses composition in place of inheritance

public class InstrumentedSet implements Set{

private final Set s;

private int addCount=0;

public InstrumentedSet(Set s){

this.s=s;

}

public boolean add(Object o){

addCount++;

return s.add();

}

public boolean addAll(Collection c){

addCount+=c.size();

return s.addAll(c);

}

public int getAddCount(){

return addCount;

}

//forwarding methods

public void clear() { s.clear(); }

public boolean contains(Object o){return s.contains(o); }

public boolean isEmpty() {return s.isEmpty(); }

public int size() {return s.size(); }

....

public String toString() {return s.toString(); }

}

  上例中,InstrumentedSet类对Set类进行了修饰,增加了计数特性。有时,复合和转发这两项技术的结合被错误地引用为“委托(delegation)”,从技术的角度而言,这不是委托,除非包装对象把自己传递给一个被包装的对象。

  包装类几乎没有什么缺点。需要注意的是,包装类不适合用于回调框架(callback framework)中。在回调框架中,对象把自己的引用传递给其它的对象,以便将来调用回来,当它被包装起来以后,它并不知道外面的包装对象的情况,所以它传递一个指向自己的引用(this)时,会造成回调时绕开外面的包装对象的问题。这被称为SELF问题。

  只有当子类真正是超类的“子类型(subtype)”时,继承才是合适的。即两者之存在“is-a”的关系。java平台中,也有违反这条规则的地方:如Stack不是向量,所以不应扩展Vector;属性列表不是散列表,所以Properties不应扩展Hashtable。在决定使用复合还是扩展时,还要看一试图扩展的类的API有没有缺陷,如果你愿意传播这些缺陷到自己的API中,则用继承,否则可用复合来设计一个新的API。

Posted by Hilton at February 14, 2004 09:21 PM | TrackBack

抱歉!评论已关闭.