合成複用原則詳解篇(附圖解及原始碼例項)
來源:mikechen的網際網路架構
透過合成複用,我們可以更加優雅地實現程式碼複用。
合成複用原則解決了繼承在程式碼複用時的相關問題。
本文,主要全面詳解合成複用原則的概念、背景、作用、UML、示例、應用,以及組合和繼承的選型思路。
設計模式的七大原則已全部完結,結合一起看,更易融會貫通:
1. 微信面試:說說里氏替換原則
2. 精通介面隔離原則
3. 依賴倒置原則就看這篇
4. 3 分鐘吃透開閉原則
5. 單一職責原則讓程式碼質量提升100倍
6. 通俗理解迪米特法則
合成複用原則(CRP),又稱為組合/聚合複用原則,英文全稱 Composite Reuse Principle。
合成複用原則要求在實現程式碼複用時,儘量先使用組合或聚合等關聯關係,其次考慮使用繼承關係。
合成複用原則的核心思想是:
將不同的類、模組或元件組合在一起,來建立新的類或物件。
儘量不透過繼承已有的類來獲得需要的功能。
在早期的物件導向程式設計中,程式碼複用主要用繼承。
透過繼承,一個類可以從另一個類派生,獲得其屬性和方法。
但繼承也有一些缺點:
緊耦合(Tight Coupling):繼承引入了強耦合,使得子類依賴於父類的內部實現細節。當父類發生變化時,子類會受到影響隨之變化。
繼承層次複雜性:隨著繼承層次的增加,程式碼的複雜性也會增加,繼承層次的擴充套件和維護會很複雜。
白箱複用:繼承會將父類的實現細節暴露給子類,父類對子類是透明的,破壞了類的封裝性。
限制了複用的靈活性:從父類繼承而來的實現是靜態的,在編譯時已經定義,所以在執行時不可能發生變化。
為了解決繼承帶來的問題,合成複用原則應運而生。
透過合成複用,我們可以更加優雅地實現程式碼複用。
組合的優點:
低耦合性:程式碼之間的關聯性較小,減少了類之間的緊耦合關係。
黑箱複用:維持了類的封裝性,成分物件的實現細節對新物件不可見。
複用靈活度更高:由於不受繼承關係的限制,可以適應不同的情況和需求。
確保了每個元件的單一職責原則:當一個元件需要進行更改時,只需要關注特定的元件,不需要影響整個繼承層次。
組合並沒有完全替代繼承,組合與繼承分別適用於不同的場景。
組合通常用於:
需求不斷變化的場景中,同時不受繼承關係的限制。
在類中使用其他類的功能,但不用繼承它們的所有屬性和方法。
繼承通常用於:擴充套件現有類的功能。
是否使用繼承,需要遵循里氏替換原則、Coad 法則。
3.1 里氏替換原則
里氏替換原則要求子類物件必須能夠替換其父類物件,子類應該保持與父類一致的介面和行為,不應該改變或破壞繼承來的約定。
關於里氏替換原則的介紹,推薦看這篇:
此處我們重點了解 Coad 法則。
3.2 Coad 法則
Coad 法則明確了繼承的具體使用條件:
子類是父類的特殊種類,而不是父類的一個角色。僅 is-a 關係適合繼承,而 has-a 關係應使用組合來描述。
不會出現需要將子類替換成另一個子類的情況。如果不能確定將來是否需要這種替換,就不應使用繼承。
子類應擴充套件而不是替代父類的責任。如果子類需要大規模替代父類的行為,那麼這個類不應是父類的子類。
只有在分類學意義上才可以使用繼承,不要從工具類繼承。
當 Coad 法則中的條件全部被滿足時,才應當使用繼承關係。
這裡要特別注意:
濫用繼承較為常見的錯誤是將 has-a 視為 is-a。
在這裡,is-a 表示一種類是另一種類的一種型別,而 has-a 表示一個類是另一個類的一個組成部分,不是另外一個類的特殊種類。
例如:
“人”是一個型別,“老師”、“組長”、“員工”是“人”的子類,如圖:
一個人可以同時擁有多個角色,例如,一個人可以同時是“老師”、“組長”、“員工”。
但是,按照繼承的設計邏輯,如果一個人是僱員,就不可能是經理或學生,這顯然是不合理的。
這種設計將“角色”的等級結構和“人”的等級結構混淆了。
正確的設計:
構建一個抽象類“角色”,讓“人”可以同時擁有多個“角色”(組合),“老師”、“組長”、“員工”是“角色”的子類。
另外,只有兩個類滿足里氏替換原則時,才可能是 is-a 關係。
也就是說,如果兩個類是 has-a 關係,但是設計成了繼承,就必然違反里氏替換原則。
假設:
在一個汽車分類管理程式中,汽車有兩種分類方式:
按動力源分類:汽油汽車、電動汽車等;
按顏色分類:白色汽車、黑色汽車和紅色汽車等。
如果使用繼承,就需要同時考慮這兩種分類,將會產生6個組合。
這樣的方式會帶來兩個問題:
導致子類過多。
任何一個分類發生變更,都要修改原始碼,違背了開閉原則。
圖例:
程式碼:
public abstract class Car
{
abstract void run();
}
public class ElectricCar extends Car
{
@Override
void run()
{
System.out.println("電動汽車");
}
}
public class PetrolCar extends Car {
@Override
void run()
{
System.out.println("汽油汽車");
}
}
public class BlackElectricCar extends ElectricCar
{
public void appearance()
{
System.out.print("黑色");
super.run();
}
}
public class BlackPetrolCar extends PetrolCar
{
public void appearance()
{
System.out.print("黑色");
super.run();
}
}
public class RedElectricCar extends ElectricCar
{
public void appearance()
{
System.out.print("紅色");
super.run();
}
}
public class RedPetrolCar extends PetrolCar
{
public void appearance()
{
System.out.print("紅色");
super.run();
}
}
public class WhiteElectricCar extends ElectricCar
{
public void appearance()
{
System.out.print("白色");
super.run();
}
}
public class WhitePetrolCar extends PetrolCar
{
public void appearance()
{
System.out.print("白色");
super.run();
}
}
public class Test {
public static void main(String[] args) {
RedElectricCar redElectricCar = new RedElectricCar();
redElectricCar.appearance();//紅色電動汽車
}
}
採用組合方式:
先把顏色 Color 抽象為介面,有白色,黑色,紅色三個顏色實現類;
再將 Color 物件組合在汽車 Car 類中。
最終,只需要生成 5 個類,就可以實現上述功能。
並且,後續分類有任何變更,只需要增加實現類,不要去修改原始碼。
圖例:
程式碼:
public abstract class Car
{
abstract void run();
Color color;
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
}
public interface Color
{
void colorKind();
}
public class ElectricCar extends Car
{
@Override
void run()
{
System.out.println("電動汽車");
}
}
public class PetrolCar extends Car {
@Override
void run()
{
System.out.println("汽油汽車");
}
}
public class White implements Color{
@Override
public void colorKind()
{
System.out.println("白色");
}
}
public class Black implements Color{
@Override
public void colorKind()
{
System.out.println("黑色");
}
}
public class Red implements Color{
@Override
public void colorKind()
{
System.out.println("紅色");
}
}
public class Test
{
public static void main(String[] args)
{
ElectricCar electricCar = new ElectricCar();
White color = new White();
electricCar.setColor(color);
electricCar.getColor().colorKind();//白色
electricCar.run();//電動汽車
}
}
以上,就是對合成複用原則的概念、背景、作用、UML、示例、應用,以及組合和繼承的選型思路的全面詳解。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024922/viewspace-2993263/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 設計模式 - 原則及例項講解設計模式
- 軟體設計原則—合成複用原則
- Oracle minus用法詳解及應用例項Oracle
- Linux IO模式及 select、poll、epoll詳解(含部分例項原始碼)Linux模式原始碼
- EventBus詳解及簡單例項單例
- MySQL 序列 AUTO_INCREMENT詳解及例項程式碼MySqlREM
- UML 用例圖詳解
- Vue 原始碼解讀(6)—— 例項方法Vue原始碼
- 相親交友原始碼的架構設計,實現合成複用原則需要如何做?原始碼架構
- CSS3繪製太極圖程式碼例項詳解CSSS3
- 附例項!圖解React的生命週期及執行順序圖解React
- ArrayMap詳解及原始碼分析原始碼
- EventBus詳解及原始碼分析原始碼
- 詳解建造者模式(含圖例、UML類圖、原始碼示例等)模式原始碼
- axios模擬GET請求例項及詳解iOS
- 元件例項 $el 詳解元件
- SparseArray詳解及原始碼簡析原始碼
- LinkedHashMap 詳解及原始碼簡析HashMap原始碼
- Linuxepoll模型詳解及原始碼分析Linux模型原始碼
- Linux與windows檔案傳輸詳解及例項LinuxWindows
- 圖解 | 原來這就是 IO 多路複用圖解
- 用圖表和例項解釋 Await 和 AsyncAI
- 六張圖詳解LinkedList 原始碼解析原始碼
- CSS例項詳解:Flex佈局CSSFlex
- Spring事務管理(詳解+例項)Spring
- 7 大設計原則總結篇(41張圖解、2萬多字、非常詳細)圖解
- 單一職責原則詳解
- EventBus 3.0+ 原始碼詳解(史上最詳細圖文講解)原始碼
- Android--Handler機制及原始碼詳解Android原始碼
- shiro 整合 spring 實戰及原始碼詳解Spring原始碼
- Golang WaitGroup 底層原理及原始碼詳解GolangAI原始碼
- PHP 觀察者模式應用場景例項詳解PHP模式
- IO多路複用詳解
- LINUX Shell指令碼程式設計例項詳解(一)上Linux指令碼程式設計
- 例項程式碼詳解正規表示式匹配換行
- Tomcat常見異常及解決方案程式碼例項Tomcat
- 詳解Java 容器(第③篇)——容器原始碼分析 - ListJava原始碼
- 詳解Java 容器(第④篇)——容器原始碼分析 - MapJava原始碼