第四章 类和接口 第16条 复合优先于继承

本条目讨论的问题并不适用于接口继承

与方法调用不同的是,继承打破了封装性,如果超类中特定功能的实现细节发生了变化,子类可能会遭到破坏,即使它的代码完全没有改变,除非超类是专门为了扩展而设计的,并且有很好文档说明

实例:

public class InstrumentedHashSet<E> extends HashSet<E>{
	private int addCount = 0;

	public InstrumentedHashSet() {
		super();
	}

	public InstrumentedHashSet(Collection<? extends E> c) {
		super(c);
	}

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

	public InstrumentedHashSet(int initialCapacity) {
		super(initialCapacity);
	}

	@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;
	}
	
	
}

这个类看起来正常,实际上它并不能工作

InstrumentedHashSet ih = new InstrumentedHashSet<>();
		ih.addAll(Arrays.asList(new String[] {"1","2","3"}));
		System.out.println(ih.getAddCount());

我们期望的结果是3,但是实际结果是6,这是因为 addAll调用了add方法

add方法这种"自用性",是实现细节,不是承诺,它不能保证在所有实现类中不变,不能保证随着发行版本的不同而发生变化,所以这种类是非常脆弱的

稍微正确的做法是,子类模仿超类的方式,在addAll中调用add,这样能得到正确的结果, 这样做困难,而且容易出错:最重要的一点,子类无法访问超类的私有域,所以有的方法没办法使用

还有一个安全问题:子类先扩展 超类在后期的维护中添加了这个方法,那么使用多态的时候,子类可能被破坏,发生意想不到的结果

如果不覆盖父类,只进行扩展,这种方式比较安全一些,但也不完全没有风险,如果超类中的新增了一个和子类中方法名相同的方法,但是返回结果不同,那么就无法通过编译,即使相同,又会回到上面的安全问题

解决办法:

public class InstrumentedHashSet<E> extends ForwardingSet<E>{
	public InstrumentedHashSet(Set s) {
		super(s);
	}

	private int addCount = 0;

	@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 class ForwardingSet<E> implements Set<E>{
	private final Set<E> s;
	
	public ForwardingSet(Set s) {
		this.s = s;
	}
	@Override
	public int size() {
		return s.size();
	}

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

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

	@Override
	public Iterator<E> iterator() {
		return s.iterator();
	}

	@Override
	public Object[] toArray() {
		return s.toArray();
	}

	@Override
	public <T> T[] toArray(T[] a) {
		return s.toArray(a);
	}

	@Override
	public boolean add(E e) {
		return s.add(e);
	}

	@Override
	public boolean remove(Object o) {
		return s.remove(o);
	}

	@Override
	public boolean containsAll(Collection<?> c) {
		return s.containsAll(c);
	}

	@Override
	public boolean addAll(Collection<? extends E> c) {
		return s.addAll(c);
	}

	@Override
	public boolean retainAll(Collection<?> c) {
		return s.retainAll(c);
	}

	@Override
	public boolean removeAll(Collection<?> c) {
		return s.removeAll(c);
	}

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

在新类中增加一个私有域,它引用现有类的一个实例,这种设计被称做"复合",因为现有的类变成了新类的一个组件,新类中的每个方法都可以调用现有类实例对应的方法,并返回它的结果.这被称为转发,新类中方法被称为转发方法,这样可以实现很好的分离,注意这个实现分为两部分:类本身和可重用的转发类,包含所有的转发方法,没有其它方法

包装类的优势:可以实现多态,接收任何set 及其子类,为其加上计数方法

转发类的优势:可重用,解除耦合

测试:

InstrumentedHashSet ih = new InstrumentedHashSet<>(new TreeSet<>());
		ih.addAll(Arrays.asList(new String[] {"1","2","3"}));
		System.out.println(ih.getAddCount());

总结:

继承需要慎用:考虑清楚是否是"is-a"的关系


猜你喜欢

转载自blog.csdn.net/perfect_red/article/details/80457610