首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > JAVA > J2SE开发 >

继承有关问题(复合优于继承)

2013-01-23 
继承问题(复合优于继承)最近在读Effective Java这本书,讲到了item 16:复合优先于继承1.说是一个类,如果继

继承问题(复合优于继承)
最近在读Effective Java这本书,讲到了item 16:复合优先于继承
1.说是一个类,如果继承了另一个类。有可能,父类在后期版本中加了一个新的方法,但是这个方法名在子类中已经存在。这就会出现,编译错误,或者父类的新方法被子类覆盖的问题。
2.然后提出了用复合取代继承。即把原来的“子类”增加一个私有的成员变量,指向“父类”的一个实例。可以解决上面提出的问题。(注,这里的“子类”,“父类”只是为了好描述问题,没改变之前,他们是有继承关系的)
3.他们给出了两个例子来说明问题。
4.我把他们的例子拿来跑了一下,好像问题没解决。还是我哪里理解错了?望大神们解答一下
5.我的问题主要在代码的注释里,麻烦大家看一下。有没看清楚问题的可以提问
6.先说 一以下代码要实现的功能,就是为set这个接口扩展一个计数器。

代码一:继承的(也即是书中提到的有问题的代码)


public class InstrumentedHashSet<E> extends HashSet<E> {

private int addCount = 0;
public InstrumentedHashSet(){}

public InstrumentedHashSet(int initCap, float loadFactor){
super(initCap, loadFactor);
}

@Override
public boolean add(E e){
addCount++;
return super.add(e);
}
@Override 
public boolean addAll(Collection<? extends E> c){
addCount += c.size();
return super.addAll(c);
}
public int getAddCount(){
return addCount;
}
public static void main(String[] args) {
InstrumentedHashSet<String> s = new InstrumentedHashSet<String>();
s.addAll(Arrays.asList("","",""));
System.out.println(s.getAddCount());//这里如果正确的话,应该输出3,但是这里输出6。他们的解释是因为addAll方法调用的是add方法。即新类受到了父类的影响,所以提出了下面的解决方法
}
}


代码二:把继承改为复合的(也即是书中提到的修复问题的代码)

//Wrapper class - use composition in place of inheritance
public class InstrumentedHashSet2<E> extends ForwardingSet<E>{

private int addCount = 0;
public InstrumentedHashSet2(Set<E> s) {
super(s);
}

@Override
public boolean add(E e){
addCount++;
return super.add(e);
}
@Override 
public boolean addAll(Collection<? extends E> c){
addCount += c.size();
return super.addAll(c);
}

public int getAddCount(){
return addCount;
}

public static void main(String[] args) {
InstrumentedHashSet<String> s = new InstrumentedHashSet<String>();
s.addAll(Arrays.asList("","",""));
System.out.println(s.getAddCount());//我觉得既然是改进的代码,这里就应该输出3了,但是怎么还是输出6.
}
}
/*
 * 
**/
class ForwardingSet<E> implements Set<E>{
//改进的地方:没有继承HashSet或者TreeSet,而是在这里加了一个私有成员变量。
         //文中提到,这样写,不依赖于现有的类(Set)的实现,不会受到现有类(Set)的影响
         //我怎么觉得还是会受到影响呢?比如Set添加了一个新方法A(),还是会影响到上面那个类的(如果上面那个类已经存在A(),上面那个类也会覆盖Set中的A()方法)
private final Set<E> s;//增加一个私有的成员变量
public ForwardingSet(Set<E> s){this.s = s;}

@Override
public boolean add(E e) {return s.add(e);}
@Override
public boolean addAll(Collection<? extends E> c) {return s.addAll(c);}
        //省略剩下方法的复写,但是和上面两个方法类似,由于没用到,我也就不拿出来了,有点长

}

------解决方案--------------------


楼主,你复合的类是InstrumentedHashSet2,你看看你调用的是什么类。
你调用的还是InstrumentedHashSet类(继承的),所以结果不对了,你粗心啊!
[解决办法]
这个是个非常经典的复合优于继承的例子,希望楼主能够消化。
[解决办法]
继承时,应该往往涉及到多态,这个就得格外注意一点,特别是这个例子,父类中有add(),addAll()子类中也有add(),addAll(),并且父类addAll()调用了add(),嵌套了。
[解决办法]
这样做等同于新建一个类,而这个类的全部作用就是给原来继承的父类的所有已知方法进行代理。
代理方法是死的,不会随原父类方法的扩充而改变,他只是调用原父类的方法,所以,当原父类新增方法时这个代理的类是不知道也不会受到影响的。
这样做也是有缺陷的:
虽然这样不会因为父类的扩展和子类发生冲突,但同样的父类扩展的好处也不会被子类自动继承,这时需要手动去更新代理类,设想一下,在一个比较复杂的系统中,所有继承关系都使用这种复合关系,那么任何非叶级的类增加方法都要在该类的复合类中增加代理方法,代码维护工作量会增加,而且父类增加方法时无法立即知道新方法与子类可能存在的冲突,而导致了重复的工作。
一般父类扩展方法与子类发生冲突是因为子类在扩展方法时没有从对象分类上进行充分的考虑导致在错误的分类范围定义错误的行为,是可以从设计上就避免的问题。
[解决办法]

引用:
这样做等同于新建一个类,而这个类的全部作用就是给原来继承的父类的所有已知方法进行代理。
代理方法是死的,不会随原父类方法的扩充而改变,他只是调用原父类的方法,所以,当原父类新增方法时这个代理的类是不知道也不会受到影响的。
这样做也是有缺陷的:
虽然这样不会因为父类的扩展和子类发生冲突,但同样的父类扩展的好处也不会被子类自动继承,这时需要手动去更新代理类,设想一下,在一个比较复杂的系统中,所有……


学习!
[解决办法]
引用:
引用:这样做等同于新建一个类,而这个类的全部作用就是给原来继承的父类的所有已知方法进行代理。
代理方法是死的,不会随原父类方法的扩充而改变,他只是调用原父类的方法,所以,当原父类新增方法时这个代理的类是不知道也不会受到影响的。
这样做也是有缺陷的:
虽然这样不会因为父类的扩展和子类发生冲突,但同样的父类扩展的好处也不会被子类自动继承,这时……

当Set进行扩展的时候,并不会影响ForwardingSet,也就是说发生冲突的方法不会被自动加入ForwardingSet中,所以不存在扩展父类直接导致与子类产生冲突的编译错误。
ForwardingSet实现Set扩展的方法时将有机会对冲突主动的避免,因为ForwardingSet只是代理Set的方法,每人要求你必须按照Set要求的格式去做,你可以在ForwardingSet里对产生冲突的方法进行改变。
[解决办法]
引用:
这样做等同于新建一个类,而这个类的全部作用就是给原来继承的父类的所有已知方法进行代理。
代理方法是死的,不会随原父类方法的扩充而改变,他只是调用原父类的方法,所以,当原父类新增方法时这个代理的类是不知道也不会受到影响的。
这样做也是有缺陷的:
虽然这样不会因为父类的扩展和子类发生冲突,但同样的父类扩展的好处也不会被子类自动继承,这时需要手动去更新代理类,设想一下,在……

赞一个
[解决办法]
引用:
ForwardingSet实现Set接口。Set进行扩展时不会影响ForwardingSet,且"也就是说发生冲突的方法不会被自动加入ForwardingSet"?为什么?ForwardingSet实现接口的话不是一定要实现接口的方法吗? 

既然是比较组合和继承,怎么又引入了implements?
[解决办法]
引用:
引用:当Set进行扩展的时候,并不会影响ForwardingSet,也就是说发生冲突的方法不会被自动加入ForwardingSet中,所以不存在扩展父类直接导致与子类产生冲突的编译错误。
 ForwardingSet实现Set扩展的方法时将有机会对冲突主动的避免,因为ForwardingSet只是代理Set的方法,每人要求你必须按照Set要……

以你给出的代码来看,原父类为HashSet,代理类实现Set接口,这两点并没有冲突,毕竟HashSet也实现了Set接口。看来要扩展的是一个Set接口的实现类,而不是Set接口本身,那个终态的私有变量也只是被指定为Set类型,所以这个实例可以是任何Set的实现类型,如你在主题中所说的HashSet或TreeSet都可以。也就是说以这个Set实例为数据源,扩展对其的操作方法,因为要扩展的方法getAddCount对元素是否有序并没有要求,但如果你要扩展方法是要求有序的或要求高速随机取值的,那还是要继承对应的Set的实现类,而且我认为实际应用当中这种情况肯定要更多一些。
[解决办法]
引用:
引用:既然是比较组合和继承,怎么又引入了implements?
这个我也没想明白。Effective Java书上给的例子。或者xodbc知道为什么?我觉得不实现这个接口也可以


引用:以你给出的代码来看,原父类为HashSet,代理类实现Set接口,这两点并没有冲突
嗯,我不是说Has……

....10楼我的确说错了,要扩展的类型是HashSet,代理类型是ForwardingSet,HashSet扩展时不会影响ForwardingSet
[解决办法]
引用:
引用:引用:
引用:既然是比较组合和继承,怎么又引入了implements?
这个我也没想明白。Effective Java书上给的例子。或者xodbc知道为什么?我觉得不实现这个接口也可以


引用:以你给出的代码来看,原父类……


实现set接口是为了达到类型兼容的目的.....不然你用继承代理类的子类的时候怎么能确定它是一个Set,而不是一个List?而且还需要规范代理类的基本行为。
[解决办法]
引用:
引用:实现set接口是为了达到类型兼容的目的.....不然你用继承代理类的子类的时候怎么能确定它是一个Set,而不是一个List?而且还需要规范代理类的基本行为。
有道理。这样不会受到实现类的影响(比如HashSet),但是还是会受到Set扩展的影响,对吗?

是的,不过接口一般不会被扩展,扩展接口是通过新建一个继承于需要扩展的接口的新接口来实现。
如果真的要扩展接口,那所有实现该接口的类都要改,这是无法避免的。
[解决办法]
接口扩展完全可以通过新增接口来实现

热点排行