Java复合优先于继承

复合优于继承

继承打破了封装性(子类依赖父类中特定功能的实现细节)

合理的使用继承的情况:

  • 在包内使用
  • 父类专门为继承为设计,并且有很好的文档说明,存在is-a关系

只有当子类真正是父类的子类型时,才适合用继承。

对于两个类A和B,只有两者之间存在"is-a"关系,类B才能拓展类A。

继承机制会把父类API中的所有缺陷传播到子类中,而复合允许设计新的API来隐藏这些缺陷。

复合(composition):不扩展现有的类,而是在新的类中增加一个私有域,引用现有类的一个实例。

转发(fowarding):新类中的每个实例方法都可以调用被包含的现有类实例中对应的方法,并返回结果。

  1 public class FowardSet implements Set {   #转发类,被装饰类
  2 
  3     //引用现有类的实例,增加私有域
  4     private final Set set;
  5 
  6     public FowardSet(Set set){
  7         this.set = set;
  8     }
  9 
 10 
 11     /*
 12      *转发方法
 13      */
 14     @Override
 15     public int size() {
 16         return set.size();
 17     }
 18 
 19     @Override
 20     public boolean isEmpty() {
 21         return set.isEmpty();
 22     }
 23 
 24     @Override
 25     public boolean contains(Object o) {
 26         return set.contains(o);
 27     }
 28 
 29     @NotNull
 30     @Override
 31     public Iterator iterator() {
 32         return set.iterator();
 33     }
 34 
 35     @NotNull
 36     @Override
 37     public Object[] toArray() {
 38         return set.toArray();
 39     }
 40 
 41     @NotNull
 42     @Override
 43     public  T[] toArray(T[] a) {
 44         return set.toArray(a);
 45     }
 46 
 47     @Override
 48     public boolean add(E e) {
 49         return set.add(e);
 50     }
 51 
 52     @Override
 53     public boolean remove(Object o) {
 54         return set.remove(o);
 55     }
 56 
 57     @Override
 58     public boolean containsAll(Collection c) {
 59         return set.containsAll(c);
 60     }
 61 
 62     @Override
 63     public boolean addAll(Collectionextends E> c) {
 64         return set.addAll(c);
 65     }
 66 
 67     @Override
 68     public boolean retainAll(Collection c) {
 69         return set.retainAll(c);
 70     }
 71 
 72     @Override
 73     public boolean removeAll(Collection c) {
 74         return set.removeAll(c);
 75     }
 76 
 77     @Override
 78     public void clear() {
 79         set.clear();
 80     }
 81 
 82     @Override
 83     public boolean equals(Object obj) {
 84         return set.equals(obj);
 85     }
 86 
 87     @Override
 88     public String toString() {
 89         return set.toString();
 90     }
 91 
 92     @Override
 93     public int hashCode() {
 94         return set.hashCode();
 95     }
 96 }
 97 
 98 /*
 99  * 包装类(wrapper class),采用装饰者模式
100  */
101 public class InstrumentedSet extends FowardSet {
102     private int addCount=0;
103 
104     public InstrumentedSet(Set set) {
105         super(set);
106     }
107 
108     @Override
109     public boolean add(E e) {
110         addCount++;
111         return super.add(e);
112     }
113 
114     @Override
115     public boolean addAll(Collectionextends E> c) {
116         addCount+=c.size();
117         return super.addAll(c);
118     }
119 
120     public int getAddCount() {
121         return addCount;
122     }
123 }

 

上面的例子中,FowardingSet是转发类,也是被包装类,而InstrumentedSet是包装类,它采用的是装饰者模式,而不是委托模式。

装饰者模式:

 装饰者模式挺像一种组合、而且是可以任意搭配、制定的。当我们有新的需求的时候、添加一个装饰器就ok。必要的时候可以添加组件、这样就实现了不用修改现有代码就可以扩展和修改新的功能的一个目的。还是那个设计原则——open for extension, close for modification.

动态地将责任附加到对象上.若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
装饰者和被装饰者之间必须是一样的类型,也就是要有共同的超类。在这里应用继承并不是实现方法的复制,而是实现类型的匹配。因为装饰者和被装饰者是同一个类型,因此装饰者可以取代被装饰者,这样就使被装饰者拥有了装饰者独有的行为。根据装饰者模式的理念,我们可以在任何时候,实现新的装饰者增加新的行为。如果是用继承,每当需要增加新的行为时,就要修改原程序了。

尽量使功能独立拆分解耦,每种新的功能分离开来称为一个装饰者,当需要的时候,就可以组合在一起使用,而不是像单独使用继承那样将多个功能耦合在一起,阅读和使用不方便,并且不利用扩展,比如 有接口A 有功能1 2 3 4 5 如果单单使用继承,那么为了使结构符合面向对象编程,将会组合成10个子类,当功能进一步扩展的时候,数量时恐怖的,并且将是很难被使用者记忆和理解的。当我们使用了装饰者模式之后,仅仅需要实现数个装饰者,然后根据需要进行组合使用就可以了。

包装类不适合用在回调框架(callback framework)中,会出现SELF问题

在回调框架中,对象把自身的引用传递给其他对象,用于后续的调用(回调)

SELF问题:被包装的对象并不知道它外面的包装对象,所以它传递一个指向自身的引用(this),回调时却避开了外面的包装对象。

简而言之,继承的功能非常强大,但也存在诸多问题,因为违背了封装原则。只有当子类和超类确实存在子类型关系时,使用继承才是恰当的,但如果子类和超类在不同包中,并且超类并不是为了继承而设计的,那么继承会导致脆弱性,为了避免这种脆弱性,可以用符合和转发机制来代替继承,尤其是当存在适当的接口实现包装类的时候。包装类不仅比子类更加健壮,而且功能更加强大。

如何从继承和复合之间做出选择?
比较抽象的说法是,只有子类和父类确实存在"is-a"关系的时候使用继承,否则使用复合。
或者比较实际点的说法是,如果子类只需要实现超类的部分行为,则考虑使用复合。

 

你可能感兴趣的:(Java复合优先于继承)