• Java复合优先于继承


    复合优于继承

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

    合理的使用继承的情况:

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

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

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

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

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

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

      1 public class FowardSet<E> implements Set<E> {   #转发类,被装饰类
      2 
      3     //引用现有类的实例,增加私有域
      4     private final Set<E> set;
      5 
      6     public FowardSet(Set<E> 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<E> 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> 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(Collection<? extends 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<E> extends FowardSet<E> {
    102     private int addCount=0;
    103 
    104     public InstrumentedSet(Set<E> 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(Collection<? extends 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"关系的时候使用继承,否则使用复合。
    或者比较实际点的说法是,如果子类只需要实现超类的部分行为,则考虑使用复合。

  • 相关阅读:
    debugs
    MySQL事务隔离级别详解(转)
    解决SQL Server管理器无法连接远程数据库的问题(转)
    jQuery中click(),bind(),live()的区别(转)
    各jQuery选择器的用法(转)
    使用JQuery获取对象的几种方式(转)
    HTML中元素的定位方式
    深入浅出REST(转载)
    jQuery-Selectors(选择器)的使用(二、层次篇)(转载)
    linux常用命令
  • 原文地址:https://www.cnblogs.com/jjfan0327/p/6961707.html
Copyright © 2020-2023  润新知