合成/聚合複用原則:學習
在一個新的對象裏面使用一些已有的對象,使之成爲新對象的一部分;新的對象經過向這些對象的委派達到複用已有功能的目的。簡述爲:儘可能使用合成/聚合,儘可能不要使用繼承。this
合成:spa
合成表示一種強的擁有關係,體現了嚴格的部分和總體的關係,部分和總體的生命週期是同樣的。一個合成的新對象徹底擁有對其組成部分的支配權,包括它們的建立和湮滅等。使用程序語言的術語來講,合成而成的新對象對組成部分的內存分配、內存釋放有絕對的責任。設計
如: 一我的新出生的人,人有兩個胳膊,胳膊和人就是部分和總體的關係。人去世了,那麼胳膊也就沒用了,也就是說胳膊和人的生命週期是相同的。對象
一個合成關係中的成分對象是不能與另外一個合成關係共享的。一個成分對象在同一個時間內只能屬於一個合成關係。若是一個合成關係湮滅了,那麼全部的成分對象要麼本身湮滅全部的成分對象(這種狀況較爲廣泛)要麼就得將這一責任交給別人(較爲罕見)。繼承
例如,一我的由頭、四肢和各類器官組成,人與這些具備相同的生命週期,人死了,這些器官也就掛了。房子和房間的關係,當房子沒了,房間也不可能獨立存在。接口
class Room{生命週期
public Room createRoom(){內存
System.out.println(「建立房間」);class
return new Room();
}
}
class House{
private Room room;
public House(){
room=new Room();
}
public void createHouse(){
room.createRoom();
}
}
聚合:用來表示「擁有」關係或者總體與部分的關係。
表明部分的對象有可能會被多個表明總體的對象所共享,並且不必定會隨着某個表明總體的對象被銷燬或破壞而被銷燬或破壞,部分的生命週期能夠超越總體。(體現的是A對象包含B對象,但B對象不是A對象的一部分)例如,班級和學生,當班級畢業後,學生還存在,學生還能被別的學校、班級引用。
聚合關係UML類圖
class Student {
}
class Classes{
privateStudent student;
publicClasses(Student student){
this.student=student;
}
}
2.爲何使用合成/聚合複用,而不使用繼承複用?
在面向對象設計裏,不一樣環境中複用已有設計和實現的基本方法:
繼承複用
繼承複用經過擴展一個已有對象的實現來獲得新的功能,基類明顯的捕獲共同的屬性和方法,而子類經過增長新的屬性和方法來擴展超類的實現。繼承是類型的複用。
優勢。
(1)新的實現較爲容易,由於超類的大部分功能能夠經過繼承關係自動進入子類。
(2)修改或擴展繼承而來的實現較爲容易。
缺點。
(1)繼承複用破壞包裝,由於繼承將父類的實現細節暴露給了子類。由於父類的內部細節經常對子類是透明的,所以這種複用是透明的複用,又叫「白箱」複用。
(2)若是父類的實現改變了,那麼子類的實現也不得不發生改變。所以,當一個父類發生了改變時,這種改變會傳導到一級又一級的子類,使得設計師不得不相應的改變這些子類,以適應父類的變化。
(3)從父類繼承而來的實現是靜態的,不可能在運行時間內發生變化,所以沒有足夠的靈活性。
好比:張全蛋有吸菸、喝酒、讀書的愛好,張全蛋生了一個女兒,女兒繼承了全蛋的愛好。女兒就擁有了全蛋的愛好,並且女兒也知道了全蛋的祕密,全蛋毫無隱私。若是有一天全蛋忽然喜歡燙頭了,那麼女兒也會發生改變,喜歡燙頭。若是隻想讓女兒繼承全蛋讀書的愛好,其餘的捨棄呢?能夠看的出來繼承是能達到複用的效果,可是不夠靈活。若是說女兒想要什麼愛好,直接從父親那裏學會更好點。
因爲繼承複用有以上的缺點,全部儘可能使用合成/聚合而不是繼承來達到對實現的複用,是很是重要的設計原則。
合成/聚合複用
因爲合成或聚合能夠將已有對象歸入到新對象中,使之成爲新對象的一部分,所以新對象能夠調用已有對象的功能。(全蛋女兒拉過來父親學習。。)
(1)新對象存取成分對象的惟一方法是經過成分對象的接口。
(2)這種複用是黑箱複用,由於成分對象的內部細節是新對象看不見的(女兒不知道全蛋有什麼祕密,全蛋很開心)。
(3)這種複用支持包裝。
(4)這種複用所需的依賴較少。
(5)每個新的類能夠將焦點集中到一個任務上。
(6) 這種複用能夠再運行時間內動態進行,新對象能夠動態地引用與成分對象類型相同的對象。
通常而言,若是一個角色獲得了更多的責任,那麼可使用合成/聚合關係將新的責任委派到合適的對象。固然,這種複用也有缺點。最主要的缺點就是經過這種複用建造的系統會有較多的對象須要管理。
3.從代碼重構的角度理解
區分「Has-A」和「Is -A」
「Is-A」是嚴格的分類學意義上的定義,意思是一個類是另以個類的「一種」。而「Has-A」表示某一個角色具備某一項責任。
致使錯誤的使用繼承而不是合成/聚合的一個常見緣由是錯誤的把「Has-A」當作「Is-A」。「Is-A」表明一個類是另外一個類的一種;「Has-A」表明一個類是另外一個類的一個角色,而不是另外一個類的一個特殊種類。
下面類圖中描述的例子。「人」被繼承到「學生」、「經理」和「僱員」等子類。而實際上,學生」、「經理」和「僱員」分別描述一種角色,而「人」能夠同時有幾種不一樣的角色。好比,一我的既然是「經理」,就必然是「僱員」;而「人」可能同時還參加MBA課程,從而也是一個「學生」。使用繼承來實現角色,則只能使每個「人」具備Is-A角色,並且繼承是靜態的,這會使得一個「人」在成爲「僱員」身份後,就永遠爲「僱員」,不能成爲「學生」和「經理」,而這顯然是不合理的。
這一錯誤的設計源自於把「角色」的等級結構和「人」的等級結構混淆起來,把「Has-A」角色誤解爲「Is -A」角色。所以要糾正這種錯誤,關鍵是區分「人」與「角色」的區別。下圖所示的的設計就正確的作到了這一點。
從上圖能夠看出,每個「人」均可以有一個以上的「角色」,全部一個「人」能夠同時是「僱員」,又是「經理」,甚至同時又是「學生」。並且因爲「人」與「角色」的耦合是經過合成的,所以,角色能夠有動態的變化。一個「人」能夠開始是「僱員」,而後晉升爲「經理」,而後又因爲他參加了MBA課程,又稱爲了「學生「。
當一個類是另外一個類的角色時,不該當使用繼承描述這種關係。
與里氏代換原則聯合使用
里氏代換原則是繼承複用的基石。若是在任何可使用B類型的地方均可以使用S類型,那麼S類型才能夠稱爲B類型的子類型(SubType),而B類型才能稱爲S類型的基類型(BaseType)。
換言之,只有當每個S在任何狀況下都是一種B的時候,才能夠將S設計成B的子類。若是兩個類的關係是「Has-A」關係而不是「Is -A」,這兩個類必定違反里氏代換原則。
只有兩個類知足里氏代換原則,纔有多是「Is -A」關係。
4.總結:
組合與繼承都是重要的複用方法
優先使用組合能夠得到複用性與簡單性更佳的設計
組合與繼承能夠一塊兒工做
基本法則是:優先使用對象組合,而非(類)繼承