这是很多设计模式的初衷,设计模式的七大原则之一就是复合原则,继承虽然可以实现代码重用,但是会增加耦合度,打破了封装性。子类依赖于超类中的某些细节,如果超类随着版本升级内部发生了改变,那么所有的子类都要随之改变。
    为了具体说明,假设有一个使用HashSet的程序。 为了调整程序的性能,需要查询HashSe,从创建它之后已经添加了多少个元素(不要和当前的元素数量混淆,当元素被删除时数量也会下降)。 为了提供这个功能,编写了一个HashSet变体,它保留了尝试元素插入的数量,并导出了这个插入数量的一个访问方法。 HashSet类包含两个添加元素的方法,分别是addaddAll,所以我们重写这两个方法:

    1. package item18;
    2. import java.util.Arrays;
    3. import java.util.Collection;
    4. import java.util.HashSet;
    5. import java.util.List;
    6. /**
    7. * @author: qujundong
    8. * @date: 2020/11/27 下午9:57
    9. * @description:
    10. */
    11. public class InstrumenteHashSet<E> extends HashSet<E> {
    12. private int addCount = 0;
    13. public InstrumenteHashSet(){}
    14. public InstrumenteHashSet(int initCap, float loadFactor){
    15. super(initCap, loadFactor);
    16. }
    17. @Override
    18. public boolean add(E e){
    19. addCount ++;
    20. return super.add(e);
    21. }
    22. @Override
    23. public boolean addAll(Collection<? extends E> c){
    24. addCount += c.size();
    25. return super.addAll(c);
    26. }
    27. public int getAddCount(){return addCount;}
    28. public static void main(String[] args) {
    29. InstrumenteHashSet<String> s = new InstrumenteHashSet<>();
    30. String[] array = {"Snap", "Crackle", "Pop"};
    31. s.addAll(Arrays.asList(array));
    32. System.out.println(s.getAddCount());
    33. }
    34. }

    上述代码我们期望输出3,但是输出6,以为HashSet中addAll内部是调用的add,但是这样的细节,子类继承后可能会忽略,所以导致出错。
    导致子类脆弱的原因:

    1. 超类发生改变会导致子类出现问题
    2. 如果子类继承后并覆盖了所有超类的方法,但是当超类增加了新方法,而子类中并没有对该类覆盖时,则会出现问题,比如下例子,usePrint是超类新加的方法,本来要用超类的print,但是由于覆盖,使用了子类的。

      /**
      * @author: qujundong
      * @date: 2020/11/27 下午10:11
      * @description:
      */
      public class Person {
       public void print(){
           System.out.println("this is person");
       }
       public void usePrint(){
           System.out.println("this is usePrint, want to use person print ");
           print();
       }
      }
      public class Man extends Person {
       public void print(){
           System.out.println("this is man");
       }
      
       public static void main(String[] args) {
           Person man = new Man();
           man.usePrint();
       }
      }
      /*
      this is usePrint, want to use person print 
      this is man
      */
      
    3. 如果子类不覆盖超类方法,只是新加一些方法,开始是不会出现问题,但是当超类增加了和子类相同名字的方法时又回到了1, 2问题。

    可以使用组合来修改上述问题:

    package item18;
    
    /**
     * @author: qujundong
     * @date: 2020/11/27 下午10:22
     * @description:
     */
    // Reusable forwarding class
    import java.util.Collection;
    import java.util.Iterator;
    import java.util.Set;
    
    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();
        }
    }
    
    package item18;
    
    /**
     * @author: qujundong
     * @date: 2020/11/27 下午10:23
     * @description:
     */
    // Wrapper class - uses composition in place of inheritance
    import java.util.Collection;
    import java.util.HashSet;
    import java.util.Set;
    import java.util.TreeSet;
    
    public class ComInstrumentedSet<E> extends ForwardingSet<E> {
    
        private int addCount = 0;
    
        public ComInstrumentedSet(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) {
            TreeSet<String> set = new TreeSet<>();
            set.add("123");
            set.add("abc");
            set.add("bcd");
            ComInstrumentedSet<String> instrumenteHashSet = new ComInstrumentedSet<String>(new TreeSet<>());
            instrumenteHashSet.addAll(set);
            System.out.println(instrumenteHashSet.getAddCount());
        }
    }
    

    总结:总之,继承是强大的,但它是有问题的,因为它违反封装。 只有在子类和父类之间存在真正的子类型关系时才适用。 即使如此,如果子类与父类不在同一个包中,并且父类不是为继承而设计的,继承可能会导致脆弱性。 为了避免这种脆弱性,使用合成和转发代替继承,特别是如果存在一个合适的接口来实现包装类。 包装类不仅比子类更健壮,而且更强大。