1. 設(shè)計(jì)模式概述
1.1 軟件設(shè)計(jì)模式的產(chǎn)生背景
"設(shè)計(jì)模式"最初并不是出現(xiàn)在軟件設(shè)計(jì)中躯嫉,而是被用于建筑領(lǐng)域的設(shè)計(jì)中。
1977年美國著名建筑大師杨拐、加利福尼亞大學(xué)伯克利分校環(huán)境結(jié)構(gòu)中心主任 克里斯托夫·亞歷山大(Christopher Alexander) 在他的著作《建筑模式語言:城鎮(zhèn)祈餐、建筑、構(gòu)造》中描述了一些常見的建筑設(shè)計(jì)問題哄陶,并提出了 253
種關(guān)于對城鎮(zhèn)帆阳、鄰里、住宅屋吨、花園和房間等進(jìn)行設(shè)計(jì)的基本模式蜒谤。
1990年軟件工程界開始研討設(shè)計(jì)模式的話題,后來召開了多次關(guān)于設(shè)計(jì)模式的研討會(huì)至扰。直到1995 年鳍徽,艾瑞克·伽馬(ErichGamma)、理査德·海爾姆(Richard Helm)敢课、拉爾夫·約翰森(Ralph Johnson)阶祭、約翰·威利斯迪斯(John Vlissides) 等 4
位作者合作出版了《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》一書,在此書中收錄了 23
個(gè)設(shè)計(jì)模式直秆,這是設(shè)計(jì)模式領(lǐng)域里程碑的事件胖翰,導(dǎo)致了軟件設(shè)計(jì)模式的突破。這 4
位作者在軟件開發(fā)領(lǐng)域里也以他們的 “四人組”(Gang of Four切厘,GoF) 著稱萨咳。
1.2 軟件設(shè)計(jì)模式的概念
軟件設(shè)計(jì)模式(Software Design Pattern),又稱設(shè)計(jì)模式疫稿,是一套被反復(fù)使用宁赤、多數(shù)人知曉的、經(jīng)過分類編目的酪劫、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)扼仲。它描述了在軟件設(shè)計(jì)過程中的一些不斷重復(fù)發(fā)生的問題,以及該問題的解決方案途蒋。也就是說猛遍,它是解決特定問題的一系列套路,是前輩們的代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié),具有一定的普遍性懊烤,可以反復(fù)使用梯醒。
1.3 學(xué)習(xí)設(shè)計(jì)模式的必要性
設(shè)計(jì)模式的本質(zhì)是面向?qū)ο笤O(shè)計(jì)原則的實(shí)際運(yùn)用,是對類的封裝性腌紧、繼承性和多態(tài)性以及類的關(guān)聯(lián)關(guān)系和組合關(guān)系的充分理解茸习。
正確使用設(shè)計(jì)模式具有以下優(yōu)點(diǎn)。
- 可以提高程序員的思維能力壁肋、編程能力和設(shè)計(jì)能力号胚。
- 使程序設(shè)計(jì)更加標(biāo)準(zhǔn)化、代碼編制更加工程化浸遗,使軟件開發(fā)效率大大提高猫胁,從而縮短軟件的開發(fā)周期。
- 使設(shè)計(jì)的代碼可重用性高跛锌、可讀性強(qiáng)杜漠、可靠性高、靈活性好察净、可維護(hù)性強(qiáng)驾茴。
1.4 設(shè)計(jì)模式分類
-
創(chuàng)建型模式
用于描述“怎樣創(chuàng)建對象”,它的主要特點(diǎn)是“將對象的創(chuàng)建與使用分離”氢卡。GoF(四人組)書中提供了單例锈至、原型、工廠方法译秦、抽象工廠峡捡、建造者等 5 種創(chuàng)建型模式。
-
結(jié)構(gòu)型模式
用于描述如何將類或?qū)ο蟀茨撤N布局組成更大的結(jié)構(gòu)筑悴,GoF(四人組)書中提供了代理们拙、適配器、橋接阁吝、裝飾砚婆、外觀、享元突勇、組合等 7 種結(jié)構(gòu)型模式装盯。
-
行為型模式
用于描述類或?qū)ο笾g怎樣相互協(xié)作共同完成單個(gè)對象無法單獨(dú)完成的任務(wù),以及怎樣分配職責(zé)甲馋。GoF(四人組)書中提供了模板方法埂奈、策略、命令定躏、職責(zé)鏈账磺、狀態(tài)芹敌、觀察者、中介者垮抗、迭代器氏捞、訪問者、備忘錄借宵、解釋器等 11 種行為型模式。
2. UML圖
統(tǒng)一建模語言(Unified Modeling Language矾削,UML) 是用來設(shè)計(jì)軟件的可視化建模語言壤玫。它的特點(diǎn)是簡單、統(tǒng)一哼凯、圖形化欲间、能表達(dá)軟件設(shè)計(jì)中的動(dòng)態(tài)與靜態(tài)信息。
UML 從目標(biāo)系統(tǒng)的不同角度出發(fā)断部,定義了用例圖猎贴、類圖、對象圖蝴光、狀態(tài)圖她渴、活動(dòng)圖、時(shí)序圖蔑祟、協(xié)作圖趁耗、構(gòu)件圖、部署圖等 9 種圖疆虚。
2.1 類圖概述
類圖(Class diagram) 是顯示了模型的靜態(tài)結(jié)構(gòu)苛败,特別是模型中存在的類、類的內(nèi)部結(jié)構(gòu)以及它們與其他類的關(guān)系等径簿。類圖不顯示暫時(shí)性的信息罢屈。類圖是面向?qū)ο蠼5闹饕M成部分。
2.2 類圖的作用
- 在軟件工程中篇亭,類圖是一種靜態(tài)的結(jié)構(gòu)圖缠捌,描述了系統(tǒng)的類的集合,類的屬性和類之間的關(guān)系译蒂,可以簡化了人們對系統(tǒng)的理解鄙币。
- 類圖是系統(tǒng)分析和設(shè)計(jì)階段的重要產(chǎn)物,是系統(tǒng)編碼和測試的重要模型蹂随。
2.3 類圖表示法
2.3.1 類的表示方式
在 UML
類圖中十嘿,類使用包含類名、屬性(field) 和方法(method) 且?guī)в蟹指罹€的矩形來表示岳锁,比如下圖表示一個(gè) Employee
類绩衷,它包含 name
, age
和 address
這3個(gè)屬性,以及work()
方法。
2.3.1.1 靜態(tài)方法的表示
靜態(tài)方法(類方法)在UML類圖中通過在方法名下面加下劃線來表示咳燕。
2.3.1.2 可見性
屬性/方法名稱前加的加號和減號表示了這個(gè)屬性/方法的可見性勿决,UML
類圖中表示可見性的符號有三種:
+
:表示public-
:表示private#
:表示protected
屬性的完整表示方式是: 可見性 名稱 : 類型 [= 缺省值]
方法的完整表示方式是: 可見性 名稱(參數(shù)列表) [: 返回類型]
2.3.2 類與類之間關(guān)系的表示方式
2.3.2.1 關(guān)聯(lián)關(guān)系
關(guān)聯(lián)關(guān)系是對象之間的一種引用關(guān)系,用于表示一類對象與另一類對象之間的聯(lián)系招盲,如老師和學(xué)生低缩、師傅和徒弟、丈夫和妻子等曹货。關(guān)聯(lián)關(guān)系是類與類之間最常用的一種關(guān)系咆繁,分為一般關(guān)聯(lián)關(guān)系、聚合關(guān)系和組合關(guān)系顶籽,一般關(guān)聯(lián)關(guān)系又可以分為單向關(guān)聯(lián)玩般,雙向關(guān)聯(lián),自關(guān)聯(lián)礼饱。
2.3.2.1.1 一般關(guān)聯(lián)關(guān)系
-
單向關(guān)聯(lián)
image.png
在 UML
類圖中單向關(guān)聯(lián)用一個(gè)帶箭頭的實(shí)線表示坏为。上圖表示每個(gè)顧客都有一個(gè)地址,這通過讓 Customer
類持有一個(gè)類型為 Address
的成員變量類實(shí)現(xiàn)镊绪。
- 雙向關(guān)聯(lián)
從上圖中我們很容易看出匀伏,所謂的雙向關(guān)聯(lián)就是雙方各自持有對方類型的成員變量。
在UML
類圖中蝴韭,雙向關(guān)聯(lián)用一個(gè)不帶箭頭的直線表示帘撰。上圖中在 Customer
類中維護(hù)一個(gè) List<Product>
,表示一個(gè)顧客可以購買多個(gè)商品万皿;在 Product
類中維護(hù)一個(gè) Customer
類型的成員變量表示這個(gè)產(chǎn)品被哪個(gè)顧客所購買摧找。
- 自關(guān)聯(lián)
自關(guān)聯(lián)在 UML
類圖中用一個(gè)帶有箭頭且指向自身的線表示。上圖的意思就是Node類包含類型為 Node
的成員變量牢硅,也就是“自己包含自己”蹬耘。
2.3.2.1.2 聚合關(guān)系
聚合關(guān)系是關(guān)聯(lián)關(guān)系的一種,是強(qiáng)關(guān)聯(lián)關(guān)系减余,是整體和部分之間的關(guān)系综苔。
聚合關(guān)系也是通過成員對象來實(shí)現(xiàn)的,其中成員對象是整體對象的一部分位岔,但是成員對象可以脫離整體對象而獨(dú)立存在如筛。例如,學(xué)校與老師的關(guān)系抒抬,學(xué)校包含老師杨刨,但如果學(xué)校停辦了,老師依然存在擦剑。
在 UML
類圖中妖胀,聚合關(guān)系可以用帶空心菱形的實(shí)線來表示芥颈,菱形指向整體。下圖所示是大學(xué)和教師的關(guān)系圖:
2.3.2.1.3 組合關(guān)系
組合表示類之間的整體與部分的關(guān)系赚抡,但它是一種更強(qiáng)烈的聚合關(guān)系爬坑。
在組合關(guān)系中,整體對象可以控制部分對象的生命周期涂臣,一旦整體對象不存在盾计,部分對象也將不存在,部分對象不能脫離整體對象而存在赁遗。例如署辉,頭和嘴的關(guān)系,沒有了頭吼和,嘴也就不存在了涨薪。
在 UML
類圖中骑素,組合關(guān)系用帶實(shí)心菱形的實(shí)線來表示炫乓,菱形指向整體。下圖所示是頭和嘴的關(guān)系圖:
2.3.2.2 依賴關(guān)系
依賴關(guān)系是一種使用關(guān)系献丑,它是對象之間耦合度最弱的一種關(guān)聯(lián)方式末捣,是臨時(shí)性的關(guān)聯(lián)。在代碼中创橄,某個(gè)類的方法通過局部變量箩做、方法的參數(shù)或者對靜態(tài)方法的調(diào)用來訪問另一個(gè)類(被依賴類)中的某些方法來完成一些職責(zé)。
在 UML
類圖中妥畏,依賴關(guān)系使用帶箭頭的虛線來表示邦邦,箭頭從使用類指向被依賴的類。下圖所示是司機(jī)和汽車的關(guān)系圖醉蚁,司機(jī)駕駛汽車:
2.3.2.3 繼承關(guān)系
繼承關(guān)系是對象之間耦合度最大的一種關(guān)系燃辖,表示一般與特殊的關(guān)系,是父類與子類之間的關(guān)系网棍,是一種繼承關(guān)系黔龟。
在 UML
類圖中,泛化關(guān)系用帶空心三角箭頭的實(shí)線來表示滥玷,箭頭從子類指向父類氏身。在代碼實(shí)現(xiàn)時(shí),使用面向?qū)ο蟮睦^承機(jī)制來實(shí)現(xiàn)泛化關(guān)系惑畴。例如蛋欣,Student 類和 Teacher 類都是 Person 類的子類,其類圖如下圖所示:
2.3.2.4 實(shí)現(xiàn)關(guān)系
實(shí)現(xiàn)關(guān)系是接口與實(shí)現(xiàn)類之間的關(guān)系如贷。在這種關(guān)系中豁状,類實(shí)現(xiàn)了接口捉偏,類中的操作實(shí)現(xiàn)了接口中所聲明的所有的抽象操作。
在 UML
類圖中泻红,實(shí)現(xiàn)關(guān)系使用帶空心三角箭頭的虛線來表示夭禽,箭頭從實(shí)現(xiàn)類指向接口。例如谊路,汽車和船實(shí)現(xiàn)了交通工具讹躯,其類圖如下圖所示:
軟件設(shè)計(jì)原則是在軟件開發(fā)過程中,幫助設(shè)計(jì)出可維護(hù)缠劝、可擴(kuò)展和可重用的系統(tǒng)的指導(dǎo)方針潮梯。以下是一些常見的軟件設(shè)計(jì)原則及其權(quán)威定義和例子:
3. 軟件設(shè)計(jì)原則
在設(shè)計(jì)模式中,軟件設(shè)計(jì)原則是指導(dǎo)我們構(gòu)建可維護(hù)惨恭、可擴(kuò)展和可重用的軟件系統(tǒng)的基本準(zhǔn)則秉馏。以下是一些常見的軟件設(shè)計(jì)原則及其權(quán)威定義和例子:
3.1 單一職責(zé)原則(SRP)
單一職責(zé)原則(Single Responsibility Principle,SRP) 是軟件設(shè)計(jì)中的一個(gè)重要原則脱羡,旨在幫助開發(fā)者創(chuàng)建更清晰萝究、可維護(hù)和可重用的代碼。通過將類的職責(zé)分離锉罐,可以減少代碼之間的耦合帆竹,提高系統(tǒng)的靈活性和可理解性。在軟件開發(fā)中脓规,遵循這一原則可以有效降低復(fù)雜性栽连,減少后期修改時(shí)的風(fēng)險(xiǎn)。以下是對單一職責(zé)原則的詳細(xì)解釋和示例侨舆。
3.1.1 定義
單一職責(zé)原則:一個(gè)類應(yīng)該只有一個(gè)理由去改變秒紧,也就是說,一個(gè)類應(yīng)該僅有一個(gè)職責(zé)或功能挨下。這個(gè)原則強(qiáng)調(diào)每個(gè)模塊或類應(yīng)該專注于它的單一任務(wù)熔恢,這樣可以降低系統(tǒng)的復(fù)雜性。
3.1.2 理論背景
- 維護(hù)性:當(dāng)一個(gè)類的職責(zé)過多時(shí)复颈,如果需要修改某個(gè)功能绩聘,可能會(huì)影響到其他功能,導(dǎo)致系統(tǒng)的不穩(wěn)定和復(fù)雜性增加耗啦。
- 可理解性:職責(zé)明確的類更容易理解凿菩,開發(fā)者可以快速掌握它的功能和目的。
- 可重用性:當(dāng)一個(gè)類專注于特定職責(zé)時(shí)帜讲,它更容易被重用在不同的上下文中衅谷。
3.1.3 例子
假設(shè)我們有一個(gè) User
類,負(fù)責(zé)處理用戶信息和發(fā)送用戶的郵件似将。這個(gè)類違反了單一職責(zé)原則获黔,因?yàn)樗袚?dān)了兩個(gè)不同的職責(zé)蚀苛。
3.1.3.1 違反 SRP 的代碼示例
class User {
private String name;
private String email;
public User(String name, String email) {
this.name = name;
this.email = email;
}
public void sendEmail(String message) {
// 發(fā)送郵件的邏輯
System.out.println("Sending email to " + email + ": " + message);
}
public void displayUserInfo() {
System.out.println("User: " + name + ", Email: " + email);
}
}
在這個(gè)例子中,User
類既負(fù)責(zé)用戶信息的管理(如 displayUserInfo
方法)玷氏,又負(fù)責(zé)發(fā)送郵件(如 sendEmail
方法)堵未。這導(dǎo)致了兩個(gè)職責(zé)的耦合,違反了單一職責(zé)原則盏触。
3.1.3.2 遵循 SRP 的代碼示例
我們可以將職責(zé)分開渗蟹,創(chuàng)建一個(gè) User
類來管理用戶信息,另一個(gè) EmailService
類來處理郵件發(fā)送赞辩。
class User {
private String name;
private String email;
public User(String name, String email) {
this.name = name;
this.email = email;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public void displayUserInfo() {
System.out.println("User: " + name + ", Email: " + email);
}
}
class EmailService {
public void sendEmail(User user, String message) {
// 發(fā)送郵件的邏輯
System.out.println("Sending email to " + user.getEmail() + ": " + message);
}
}
在這個(gè)遵循 SRP 的實(shí)現(xiàn)中:
-
User
類只負(fù)責(zé)用戶信息的管理雌芽。 -
EmailService
類只負(fù)責(zé)發(fā)送郵件。
3.2 開放-關(guān)閉原則(OCP)
開放-關(guān)閉原則(Open-Closed Principle辨嗽,OCP) 是軟件設(shè)計(jì)中的一個(gè)重要原則世落,強(qiáng)調(diào)通過抽象和接口實(shí)現(xiàn)代碼的擴(kuò)展性。通過遵循 OCP糟需,可以在不修改現(xiàn)有代碼的情況下屉佳,輕松添加新功能,從而提高系統(tǒng)的靈活性篮灼、可維護(hù)性和可重用性忘古。這一原則在軟件開發(fā)中廣泛應(yīng)用徘禁,特別是在設(shè)計(jì)大型系統(tǒng)和框架時(shí)诅诱。以下是對開放-關(guān)閉原則的詳細(xì)解釋和示例。
3.2.1 定義
開放-關(guān)閉原則:軟件實(shí)體(類送朱、模塊娘荡、函數(shù)等)應(yīng)該對擴(kuò)展開放,對修改關(guān)閉驶沼。這意味著我們應(yīng)該能夠在不修改現(xiàn)有代碼的情況下炮沐,擴(kuò)展系統(tǒng)的功能。
3.2.2 理論背景
- 擴(kuò)展性:通過遵循 OCP回怜,系統(tǒng)可以在不影響現(xiàn)有功能的情況下大年,輕松添加新功能。
- 維護(hù)性:減少對現(xiàn)有代碼的修改可以降低引入錯(cuò)誤的風(fēng)險(xiǎn)玉雾,提高代碼的穩(wěn)定性翔试。
- 可重用性:通過抽象和接口,能夠?qū)崿F(xiàn)更高的代碼重用率复旬。
3.2.3 例子
假設(shè)我們有一個(gè)簡單的圖形繪制程序垦缅,它支持繪制圓形和方形。如果我們通過修改現(xiàn)有的代碼來添加新形狀(如三角形)驹碍,這將違反開放-關(guān)閉原則壁涎。
3.2.3.1 違反 OCP 的代碼示例
class GraphicEditor {
public void drawShape(Shape shape) {
if (shape instanceof Circle) {
// 繪制圓形的邏輯
} else if (shape instanceof Rectangle) {
// 繪制矩形的邏輯
}
}
}
class Circle {
// 圓形的屬性和方法
}
class Rectangle {
// 矩形的屬性和方法
}
在這個(gè)例子中凡恍,GraphicEditor
類需要根據(jù)不同的形狀類型進(jìn)行判斷,添加新形狀時(shí)需要修改 drawShape
方法怔球,這違反了開放-關(guān)閉原則嚼酝。
3.2.3.2 遵循 OCP 的代碼示例
我們可以通過引入一個(gè) Shape
接口來遵循開放-關(guān)閉原則。每種形狀實(shí)現(xiàn)該接口竟坛,GraphicEditor
類只需要調(diào)用接口的方法革半,而不需要了解具體的實(shí)現(xiàn)。
interface Shape {
void draw();
}
class Circle implements Shape {
public void draw() {
// 繪制圓形的邏輯
}
}
class Rectangle implements Shape {
public void draw() {
// 繪制矩形的邏輯
}
}
class GraphicEditor {
public void drawShape(Shape shape) {
// 調(diào)用形狀的 draw 方法
shape.draw();
}
}
在這個(gè)遵循 OCP 的實(shí)現(xiàn)中:
-
Shape
接口定義了一個(gè)draw
方法流码。 - 每種形狀(如
Circle
和Rectangle
)實(shí)現(xiàn)該接口又官,提供各自的繪制邏輯。 -
GraphicEditor
類只依賴于Shape
接口漫试,可以在不修改現(xiàn)有代碼的情況下六敬,添加新形狀(如Triangle
),只需實(shí)現(xiàn)Shape
接口即可驾荣。
3.3 里氏替換原則(LSP)
里氏替換原則(Liskov Substitution Principle外构,LSP) 是軟件設(shè)計(jì)中的一個(gè)重要原則,強(qiáng)調(diào)子類應(yīng)該能夠替代父類而不影響程序的正確性播掷。遵循這一原則有助于提高代碼的可維護(hù)性和可擴(kuò)展性审编,確保系統(tǒng)的靈活性。通過合理的設(shè)計(jì)歧匈,比如使用接口或抽象類垒酬,可以更好地實(shí)現(xiàn)這一原則,使代碼結(jié)構(gòu)更加清晰和穩(wěn)健件炉。以下是對里氏替換原則的詳細(xì)解釋和示例勘究。
3.3.1 定義
里氏替換原則:如果 S
是 T
的子類型,那么在程序中可以用 S
替代 T
斟冕,而不影響程序的正確性口糕。這意味著子類應(yīng)該能夠擴(kuò)展父類的功能,而不改變父類的期望行為磕蛇。
3.3.2 理論背景
- 可替代性:遵循 LSP景描,可以確保子類在邏輯上能夠替代父類,增強(qiáng)了代碼的靈活性和可擴(kuò)展性秀撇。
- 正確性:程序在使用父類的地方超棺,無論是使用父類還是子類,都應(yīng)保持相同的行為捌袜,確保不引入錯(cuò)誤说搅。
3.3.3 例子
假設(shè)我們有一個(gè) Bird
類和一個(gè)子類 Penguin
。根據(jù) LSP虏等,Penguin
不能是 Bird
的子類弄唧,因?yàn)槠簌Z不能飛适肠。
3.3.3.1 違反 LSP 的代碼示例
class Bird {
public void fly() {
// 飛行的邏輯
}
}
class Sparrow extends Bird {
// 麻雀會(huì)飛,繼承 Bird 的飛行行為
}
class Penguin extends Bird {
@Override
public void fly() {
// 企鵝不會(huì)飛候引,不符合預(yù)期行為
throw new UnsupportedOperationException("Penguins can't fly!");
}
}
在這個(gè)例子中侯养,Penguin
類重寫了 fly
方法,拋出了異常澄干,這違反了里氏替換原則逛揩,因?yàn)樵谑褂?Bird
的地方,無法用 Penguin
替代 Bird
麸俘。
3.3.3.2 遵循 LSP 的代碼示例
為了遵循里氏替換原則辩稽,可以將飛行和非飛行的鳥類分開,使用接口來表示飛行行為从媚。
interface Flyable {
void fly();
}
class Bird {
// 一些共同的鳥類屬性和方法
}
class Sparrow extends Bird implements Flyable {
public void fly() {
// 實(shí)現(xiàn)飛行邏輯
}
}
class Penguin extends Bird {
// 不實(shí)現(xiàn) Flyable 接口
}
在這個(gè)遵循 LSP 的實(shí)現(xiàn)中:
-
Bird
類包含所有鳥類的共享邏輯逞泄。 -
Flyable
接口定義了fly
方法,只有會(huì)飛的鳥(如Sparrow
)實(shí)現(xiàn)該接口拜效。 -
Penguin
類不實(shí)現(xiàn)Flyable
接口喷众,這樣就不會(huì)有不符合預(yù)期的行為。
3.4 依賴倒置原則(DIP)
依賴倒置原則(Dependency Inversion Principle紧憾,DIP) 是軟件設(shè)計(jì)中的一個(gè)關(guān)鍵原則到千,強(qiáng)調(diào)高層模塊和低層模塊都應(yīng)依賴于抽象,而不是具體實(shí)現(xiàn)赴穗。通過遵循這一原則憔四,可以降低系統(tǒng)的耦合度,提高靈活性和可維護(hù)性望抽,使代碼更容易進(jìn)行修改和擴(kuò)展加矛。此外履婉,依賴于抽象還增強(qiáng)了可測試性煤篙,便于進(jìn)行單元測試和模擬。以下是對依賴倒置原則的詳細(xì)解釋和示例毁腿。
3.4.1 定義
依賴倒置原則:高層模塊不應(yīng)依賴低層模塊辑奈,二者都應(yīng)依賴抽象;抽象不應(yīng)依賴細(xì)節(jié)已烤,細(xì)節(jié)應(yīng)依賴抽象鸠窗。這意味著我們應(yīng)該依賴于接口或抽象類,而不是具體的實(shí)現(xiàn)胯究。
3.4.2 理論背景
- 降低耦合:通過依賴抽象而不是具體實(shí)現(xiàn)稍计,可以減少模塊之間的耦合,提高代碼的可重用性裕循。
- 提高靈活性:系統(tǒng)更容易進(jìn)行擴(kuò)展和修改臣嚣,因?yàn)楦淖兙唧w實(shí)現(xiàn)不會(huì)影響到依賴于它的高層模塊净刮。
- 增強(qiáng)可測試性:依賴于抽象使得單元測試更容易,因?yàn)榭梢杂媚M對象替代具體實(shí)現(xiàn)硅则。
3.4.3 例子
假設(shè)我們有一個(gè) EmailService
類依賴于具體的 SmtpClient
類淹父。如果我們想要更改發(fā)送郵件的方式,必須修改 EmailService
類怎虫,違反了依賴倒置原則暑认。
3.4.3.1 違反 DIP 的代碼示例
class SmtpClient {
public void sendEmail(String message) {
// 發(fā)送郵件的邏輯
}
}
class EmailService {
private SmtpClient smtpClient;
public EmailService() {
// 依賴于具體實(shí)現(xiàn)
this.smtpClient = new SmtpClient();
}
public void send(String message) {
smtpClient.sendEmail(message);
}
}
在這個(gè)例子中,EmailService
類直接依賴于 SmtpClient
類大审,這使得它無法靈活地更改郵件發(fā)送的方式蘸际。
3.4.3.2 遵循 DIP 的代碼示例
可以通過引入一個(gè) EmailClient
接口來遵循依賴倒置原則。
interface EmailClient {
void sendEmail(String message);
}
class SmtpClient implements EmailClient {
public void sendEmail(String message) {
// 發(fā)送郵件的邏輯
}
}
class EmailService {
private EmailClient emailClient;
public EmailService(EmailClient emailClient) {
// 依賴于抽象
this.emailClient = emailClient;
}
public void send(String message) {
emailClient.sendEmail(message);
}
}
在這個(gè)遵循 DIP 的實(shí)現(xiàn)中:
-
EmailClient
接口定義了發(fā)送郵件的方法徒扶。 -
SmtpClient
類實(shí)現(xiàn)了EmailClient
接口捡鱼。 -
EmailService
類依賴于EmailClient
接口,而不是具體的SmtpClient
實(shí)現(xiàn)酷愧。
3.5 接口隔離原則(ISP)
接口隔離原則(Interface Segregation Principle驾诈,ISP) 是軟件設(shè)計(jì)中的一個(gè)重要原則,強(qiáng)調(diào)應(yīng)避免不必要的依賴溶浴,確保類只依賴于它實(shí)際需要的接口乍迄。通過遵循這一原則,可以提高系統(tǒng)的靈活性和可維護(hù)性士败,減少類之間的耦合闯两,增強(qiáng)代碼的可讀性和可理解性。這一原則在設(shè)計(jì)復(fù)雜系統(tǒng)時(shí)尤為重要谅将,能夠有效減少潛在的錯(cuò)誤和復(fù)雜性漾狼。以下是對接口隔離原則的詳細(xì)解釋和示例。
3.5.1 定義
接口隔離原則:不應(yīng)強(qiáng)迫一個(gè)類依賴于它不需要的接口饥臂。換句話說逊躁,一個(gè)接口應(yīng)該只包含客戶端所需要的方法,避免不必要的復(fù)雜性隅熙。
3.5.2 理論背景
- 減少耦合:通過將大型接口拆分成小型接口稽煤,可以減少類之間的依賴關(guān)系,使得系統(tǒng)更加靈活囚戚。
- 提高可讀性:客戶端只需要關(guān)心它所使用的接口酵熙,從而提高代碼的可讀性和理解性。
- 增強(qiáng)可維護(hù)性:當(dāng)接口過于龐大時(shí)驰坊,修改接口可能會(huì)影響多個(gè)實(shí)現(xiàn)匾二,導(dǎo)致代碼不易維護(hù)。隔離接口可以減少這種影響。
3.5.3 例子
假設(shè)我們有一個(gè) Animal
接口察藐,包含所有動(dòng)物的行為借嗽,包括 fly
和 swim
方法。如果一個(gè) Dog
類實(shí)現(xiàn)這個(gè)接口转培,那么它將需要實(shí)現(xiàn) swim
方法恶导,盡管狗并不會(huì)游泳,這違反了接口隔離原則浸须。
3.5.3.1 違反 ISP 的代碼示例
interface Animal {
void fly();
void swim();
void walk();
}
class Dog implements Animal {
@Override
public void fly() {
throw new UnsupportedOperationException("Dogs can't fly!");
}
@Override
public void swim() {
// 實(shí)現(xiàn)游泳邏輯
}
@Override
public void walk() {
// 實(shí)現(xiàn)行走邏輯
}
}
在這個(gè)例子中惨寿,Dog
類被迫實(shí)現(xiàn) Animal
接口的所有方法,包括不適用的方法删窒。
3.5.3.2 遵循 ISP 的代碼示例
可以將接口拆分為更小的接口裂垦,使每個(gè)接口只包含相關(guān)的方法。
interface Walkable {
void walk();
}
interface Flyable {
void fly();
}
interface Swimmable {
void swim();
}
class Dog implements Walkable {
@Override
public void walk() {
// 實(shí)現(xiàn)行走邏輯
}
}
class Bird implements Walkable, Flyable {
@Override
public void walk() {
// 實(shí)現(xiàn)行走邏輯
}
@Override
public void fly() {
// 實(shí)現(xiàn)飛行邏輯
}
}
在這個(gè)遵循 ISP 的實(shí)現(xiàn)中:
-
Walkable
肌索、Flyable
和Swimmable
接口被拆分蕉拢,包含各自相關(guān)的方法。 -
Dog
類只實(shí)現(xiàn)Walkable
接口诚亚,而Bird
類實(shí)現(xiàn)Walkable
和Flyable
接口晕换。
3.6 迪米特法則(LoD)
迪米特法則(Law of Demeter,LoD)站宗,也稱為“最少知識原則”闸准,是軟件設(shè)計(jì)中的一個(gè)重要原則,強(qiáng)調(diào)對象之間的交互應(yīng)盡量簡化梢灭,減少對陌生對象的依賴夷家。通過遵循這一原則,可以降低系統(tǒng)的耦合度敏释,提高代碼的可維護(hù)性和可理解性库快。這一原則特別適用于復(fù)雜系統(tǒng)的設(shè)計(jì),有助于實(shí)現(xiàn)模塊的獨(dú)立性和靈活性钥顽。以下是對迪米特法則的詳細(xì)解釋和示例义屏。
3.6.1 定義
迪米特法則:一個(gè)對象應(yīng)該對其他對象有最少的了解,即一個(gè)對象只應(yīng)該調(diào)用直接的朋友(直接依賴的對象)的方法耳鸯,而不應(yīng)該調(diào)用陌生人的方法或朋友的朋友的方法湿蛔。
3.6.2 理論背景
- 降低耦合:通過限制對象之間的交互,可以降低系統(tǒng)的耦合度县爬,增強(qiáng)模塊的獨(dú)立性。
- 提高可維護(hù)性:減少對象之間的依賴關(guān)系添谊,使得系統(tǒng)更易于維護(hù)和修改财喳,降低了引入錯(cuò)誤的風(fēng)險(xiǎn)。
- 增強(qiáng)可理解性:對象之間的交互變得更加清晰,便于理解和調(diào)試耳高。
3.6.3 例子
假設(shè)有一個(gè) Order
類扎瓶,它需要獲取 Customer
的地址來進(jìn)行配送。如果 Order
類直接訪問 Customer
的 Address
對象并進(jìn)一步調(diào)用方法泌枪,這就違反了迪米特法則概荷。
3.6.3.1 違反 LoD 的代碼示例
class Address {
public String getStreet() {
return "123 Main St";
}
}
class Customer {
private Address address;
public Address getAddress() {
return address;
}
}
class Order {
private Customer customer;
public void printShippingAddress() {
// 違反迪米特法則
String street = customer.getAddress().getStreet();
System.out.println("Shipping to: " + street);
}
}
在這個(gè)例子中,Order
類直接依賴于 Customer
和 Address
的實(shí)現(xiàn)碌燕,導(dǎo)致耦合度過高误证。
3.6.3.2 遵循 LoD 的代碼示例
可以通過引入一個(gè)方法,讓 Customer
類直接返回地址信息修壕,減少 Order
類對內(nèi)部實(shí)現(xiàn)的了解愈捅。
class Address {
public String getStreet() {
return "123 Main St";
}
}
class Customer {
private Address address;
public String getShippingAddress() {
// 提供直接訪問方法
return address.getStreet();
}
}
class Order {
private Customer customer;
public void printShippingAddress() {
// 遵循迪米特法則
String street = customer.getShippingAddress();
System.out.println("Shipping to: " + street);
}
}
在這個(gè)遵循 LoD 的實(shí)現(xiàn)中:
-
Customer
類提供了getShippingAddress
方法,直接返回地址信息慈鸠。 -
Order
類只與Customer
交互蓝谨,而不直接訪問Address
,從而減少了對其內(nèi)部實(shí)現(xiàn)的了解青团。
3.7 合成復(fù)用原則(Composition over Inheritance)
合成復(fù)用原則(Composition over Inheritance) 是一種軟件設(shè)計(jì)原則譬巫,強(qiáng)調(diào)通過聚合或者組合等關(guān)聯(lián)關(guān)系來實(shí)現(xiàn)代碼復(fù)用,而不是依賴于繼承督笆。這一原則旨在提高系統(tǒng)的靈活性缕题、可維護(hù)性和可擴(kuò)展性。以下是對合成復(fù)用原則的詳細(xì)解釋和示例胖腾。
3.7.1 定義
合成復(fù)用原則:優(yōu)先使用對象組合來實(shí)現(xiàn)代碼復(fù)用烟零,而不是使用類繼承。通過組合多個(gè)對象咸作,創(chuàng)建新的功能锨阿,而不是通過繼承單個(gè)類來擴(kuò)展功能。
3.7.2 理論背景
- 降低耦合:通過組合记罚,可以減少類之間的耦合墅诡,增強(qiáng)系統(tǒng)的靈活性和可維護(hù)性。
- 提高靈活性:組合允許動(dòng)態(tài)地選擇和改變行為桐智,而繼承是在編譯時(shí)確定的末早,較少靈活。
- 避免繼承的缺陷:繼承會(huì)引入復(fù)雜的類層次結(jié)構(gòu)说庭,可能導(dǎo)致“菱形繼承”等問題然磷,使得代碼難以理解和維護(hù)。
3.7.3 例子
假設(shè)我們有一個(gè) Bird
類刊驴,它有一個(gè) fly
方法姿搜。如果我們使用繼承來創(chuàng)建 FlyingBird
和 NonFlyingBird
類寡润,這可能會(huì)導(dǎo)致代碼的復(fù)雜性。
3.7.3.1 使用繼承的代碼示例
class Bird {
public void eat() {
System.out.println("Eating...");
}
}
class FlyingBird extends Bird {
public void fly() {
System.out.println("Flying...");
}
}
class NonFlyingBird extends Bird {
// 不會(huì)飛的鳥
}
在這個(gè)例子中舅柜,FlyingBird
和 NonFlyingBird
是通過繼承 Bird
類來實(shí)現(xiàn)的梭纹。如果我們需要添加其他特性(如游泳),將會(huì)使得類的層次結(jié)構(gòu)變得復(fù)雜致份。
3.7.3.2 遵循合成復(fù)用原則的代碼示例
我們可以通過組合不同的行為來遵循合成復(fù)用原則变抽。
interface Flyable {
void fly();
}
interface Swimmable {
void swim();
}
class Bird {
public void eat() {
System.out.println("Eating...");
}
}
class FlyingBird extends Bird implements Flyable {
public void fly() {
System.out.println("Flying...");
}
}
class SwimmingBird extends Bird implements Swimmable {
public void swim() {
System.out.println("Swimming...");
}
}
在這個(gè)遵循合成復(fù)用原則的實(shí)現(xiàn)中:
-
Flyable
和Swimmable
接口定義了不同的行為。 -
Bird
類依然提供基本功能氮块。 -
FlyingBird
和SwimmingBird
類通過實(shí)現(xiàn)接口組合了不同的能力绍载。