第18条 组合优先于继承
-
对于专门为了继承而设计并且具有很好的文档说明类来说,使用继承也是安全的
-
继承的缺点是,子类依赖父类中的实现细节
-
继承的问题大多数来源于覆盖(Override)父类的方法。通过继承扩展类的一个风险是,如果子类扩展了一个新方法。父类在之后的版本中巧合有了同样的方法,只有返回值不同。这时候就会发生编译错。
-
因为每一个新类的实例都把另一个现有类的实例包装起来了,所以新的类被称为包装类(wrapper class),这也正是Decorator模式。
-
只有当子类真正是超类的子类型时,才适合用继承。即对于两个类。只有当两者之间确实存在"is-a"关系的时候,才应该继承。
-
复合的书中样例:
public class InstrumentedSet<E> extends ForwardingSet<E> { private int addCount = 0; public InstrumentedSet(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 class ForwardingSet<E> implements Set<E> { private final Set<E> s; public ForwardingSet(Set<E> s) { this.s = s; } 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 Iterator<E> iterator() { return s.iterator(); } public boolean add(E e) { return s.add(e); } public boolean remove(Object o) { return s.remove(o); } public boolean containsAll(Collection<?> c) { return s.containsAll(c); } public boolean addAll(Collection<? extends E> c) { return s.addAll(c); } public boolean removeAll(Collection<?> c) { return s.removeAll(c); } public boolean retainAll(Collection<?> c) { return s.retainAll(c); } public Object[] toArray() { return s.toArray(); } public <T> T[] toArray(T[] a) { return s.toArray(a); } @Override public boolean equals(Object o) { return s.equals(o); } @Override public int hashCode() { return s.hashCode(); } @Override public String toString() { return s.toString(); } }
思考
-
书中没有直接让
InstrumentedSet
像ForwardingSet
一样,内部封装一个Set,转发所有方法然后扩展。它是多封装了一层ForwardingSet
,它的作用只是转发所有的Set方法。个人认为这样做的意义在于,如果一个项目中对Set有很多不同类型的扩展。每一个类只是一个小的扩展,但是为了使用复合进行扩展,就都需要实现Set,转发所有方法,最后添加或者修改一两个方法。这样做成本相对很高,而且重复代码变多了。所以为了满足多个类进行扩展的场景,可以先加一层ForwardingSet
,这样其它的扩展只需要继承这个类然后只编写自己的扩展部分就可以了 -
所有人都在说复合比继承的优势,那继承的优势很少有人提及。继承相对于复合,我自己想到了几点优势:
-
继承子类有一个""身份". 比如说在泛型中,一个方法入参是
Class<? extends ClassA>
,如果是用了复合来扩展ClassA
,那其实包装类是没有ClassA
的身份的,编译也没办法通过。当然这种情形可以将ClassA
的public
方法抽出来一个接口来绕过去 -
模板方法模式
标准的模板方法是这样的:
public abstract class ClassA { public void method1() { method2(); method3(); method4(); } protected abstract void method2(); private void method3() { } protected abstract void method4(); }
那它的复合版就是这样:
public class ClassA { private final ClassAble classAble; public ClassA(ClassAble classAble) { this.classAble = classAble; } public void method1() { classAble.method2(); method3(); classAble.method4(); } private void method3() { } } public interface ClassAble { void method2(); void method4(); }
这种情况下为什么模板方法模式还会存在。一方面可能是本来
ClassAble
就不是一个业务让应该存在的接口,可能是为了用复合强行抽出来的接口。另外一方面,继承相比于复合,它可以重写方法的实现,虽然可能会有副作用,它也是一种特性。 -
VO的字段扩展使用继承更合适
-
网友评论