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"關系的時候使用繼承,否則使用復合。
或者比較實際點的說法是,如果子類只需要實現超類的部分行為,則考慮使用復合。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM