在本書中,把UML中的關聯(lián)關系和聚集關系統(tǒng)稱為組合關系。組合與繼承都是提高代碼可重用性的手段。在設計對象模型時,可以按照語義來識別類之間的組合關系和繼承關系。在有些情況下,采用組合關系或者繼承關系能完成同樣的任務,組合和繼承存在著對應關系:組合中的整體類和繼承中的子類對應,組合中的局部類和繼承中的父類對應,參見表6-1。本章6.9節(jié)(小結)中的表6-2總結了組合與繼承的優(yōu)缺點。
表6-1 組合與繼承的對應關系
組 合 關 系
繼 承 關 系
局部類
父類
整體類
子類
從整體類到局部類的分解過程
從子類到父類的抽象過程
從局部類到整體類的組合過程
從父類到子類的擴展過程
的聚集關系中的整體類和局部類具有更廣泛的含義。在本章中,如果在類A中包含類C類型的屬性,那么就把類A稱為整體類或者包裝類,把類C稱為局部類或者被包裝類。
組合關系的分解過程對應繼承關系的抽象過程
圖6-11 具有相同行為的類A和類B
下面的例子未涉及具體的業(yè)務領域,該例子分別用組合關系與繼承關系來建立一個對象模型。如圖6-11所示,類A和類B有相同方法method1()、method2()和method3(),此外類A和類B還分別擁有methodA()和methodB()方法。在method3()方法中訪問method1()方法,在mehodA()和methodB()方法中都會訪問method2()方法。以下是類A和類B的源程序。
view plaincopy to clipboardprint?public class A{
private void method1(){System.out.println("method1");}
private void method2(){System.out.println("method2");}
public void method3(){method1();System.out.println("method3");}
public void methodA(){method2();System.out.println("methodA");}
}
public class B{
private void method1(){System.out.println("method1");}
private void method2(){System.out.println("method2");}
public void method3(){method1();System.out.println("method3");}
public void methodB(){method2();System.out.println("methodB");}
}
圖6-12 從類A和類B中抽象出父類C
1.使用繼承關系
在圖6-12中,從類A和類B中抽象出父類C,它包含method1()、method2()和method3()方法。由于在類A和類B中都會訪問method2()方法,因此把method2()方法聲明為protected類型。
view plaincopy to clipboardprint?public class C{
private void method1(){System.out.println("method1");}
protected void method2(){System.out.println("method2");}
public void method3(){method1();System.out.println("method3");}
}
public class A extends C{
public void methodA(){method2(); System.out.println("methodA");}
}
public class B extends C{
public void methodB(){method2(); System.out.println("methodB");}
}
2.使用組合關系
在圖6-13中,類A與類C,以及類B與類C之間為組合關系。在類A中定義了C類型的引用變量c,類A的method3()方法直接調用類C的method3()方法。類A對類C進行了封裝,類A被稱為包裝類,同樣,類B也是包裝類。由于在類A和類B中都會訪問private類型的method2()方法,因此不能把method2()方法放在類C中定義,因為如果這樣做,就必須在類C中把method2()方法定義為public類型,而這徹底破壞了封裝。以下是類C、類A和類B的源程序。
view plaincopy to clipboardprint?public class C{
private void method1(){System.out.println("method1");}
public void method3(){method1();System.out.println("method3");}
}
public class A {
private C c;
public A(C c){this.c=c;}
private void method2(){System.out.println("method2");}
public void method3(){c.method3();}
public void methodA(){method2(); System.out.println("methodA");}
}
public class B {
private C c;
public B(C c){this.c=c;}
private void method2(){System.out.println("method2");}
public void method3(){c.method3();}
public void methodB(){method2(); System.out.println("methodB");}
}
圖6-13 從類A與類B中分解出局部類C
組合關系和繼承關系相比,前者的最主要優(yōu)勢是不會破壞封裝,當類A與類C之間為組合關系時,類C封裝實現(xiàn),僅向類A提供接口;而當類A與類C之間為繼承關系時,類C會向類A暴露部分實現(xiàn)細節(jié)。在軟件開發(fā)階段,組合關系雖然不會比繼承關系減少編碼量,但是到了軟件維護階段,由于組合關系使系統(tǒng)具有較好的松耦合性,因此使得系統(tǒng)更加容易維護。
組合關系的缺點是比繼承關系要創(chuàng)建更多的對象。以下程序演示在兩種關系下創(chuàng)建類A的實例并且調用其methodA()方法。
view plaincopy to clipboardprint?//類A與類C為組合關系
C c=new C();
A a=new A(c);
a.methodA();
// 類A與類C為繼承關系
A a=new A();
a.methodA();
從以上程序看出,對于組合關系,創(chuàng)建整體類的實例時,必須創(chuàng)建其所有局部類的實例;而對于繼承關系,創(chuàng)建子類的實例時,無須創(chuàng)建父類的實例。繼承關系最大的弱點是打破了封裝,子類能夠訪問父類的實現(xiàn)細節(jié),子類與父類之間緊密耦合,子類缺乏獨立性,從而影響了子類的可維護性。為了盡可能地克服繼承的這一缺陷,應該遵循以下原則:
·精心設計專門用于被繼承的類,繼承樹的抽象層應該比較穩(wěn)定。
·對于父類中不允許覆蓋的方法,采用final修飾符來禁止其被子類覆蓋。
·對于不是專門用于被繼承的類,禁止其被繼承。
·優(yōu)先考慮用組合關系來提高代碼的可重用性。
本章對組合關系和繼承關系進行了比較,表6-2對這兩種關系的優(yōu)缺點做了總結。
表6-2 比較組合關系與繼承關系
組 合 關 系
繼 承 關 系
優(yōu)點:不破壞封裝,整體類與局部類之間松耦合,彼此相對獨立
缺點:破壞封裝,子類與父類之間緊密耦合,子類依賴于父類的實現(xiàn),子類缺乏獨立性
優(yōu)點:具有較好的可擴展性
缺點:支持擴展,但是往往以增加系統(tǒng)結構的復雜度為代價
優(yōu)點:支持動態(tài)組合。在運行時,整體對象可以選擇不同類型的局部對象
缺點:不支持動態(tài)繼承。在運行時,子類無法選擇不同的父類
優(yōu)點:整體類可以對局部類進行包裝,封裝局部類的接口,提供新的接口
缺點:子類不能改變父類的接口
缺點:整體類不能自動獲得和局部類同樣的接口
優(yōu)點:子類能自動繼承父類的接口
缺點:創(chuàng)建整體類的對象時,需要創(chuàng)建所有局部類的對象
優(yōu)點:創(chuàng)建子類的對象時,無須創(chuàng)建父類的對象