第一天:設(shè)計(jì)模式的概述、UML圖沪摄、軟件設(shè)計(jì)原則

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, ageaddress 這3個(gè)屬性,以及work() 方法。

image.png

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)系
  1. 單向關(guān)聯(lián)


    image.png

UML 類圖中單向關(guān)聯(lián)用一個(gè)帶箭頭的實(shí)線表示坏为。上圖表示每個(gè)顧客都有一個(gè)地址,這通過讓 Customer 類持有一個(gè)類型為 Address 的成員變量類實(shí)現(xiàn)镊绪。

  1. 雙向關(guān)聯(lián)
image.png

從上圖中我們很容易看出匀伏,所謂的雙向關(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è)顧客所購買摧找。

  1. 自關(guān)聯(lián)
image.png

自關(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)系圖:

image.png
2.3.2.1.3 組合關(guān)系

組合表示類之間的整體與部分的關(guān)系赚抡,但它是一種更強(qiáng)烈的聚合關(guān)系爬坑。

在組合關(guān)系中,整體對象可以控制部分對象的生命周期涂臣,一旦整體對象不存在盾计,部分對象也將不存在,部分對象不能脫離整體對象而存在赁遗。例如署辉,頭和嘴的關(guān)系,沒有了頭吼和,嘴也就不存在了涨薪。

UML 類圖中骑素,組合關(guān)系用帶實(shí)心菱形的實(shí)線來表示炫乓,菱形指向整體。下圖所示是頭和嘴的關(guān)系圖:

image.png

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ī)駕駛汽車:

image.png

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 類的子類,其類圖如下圖所示:

image.png

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)了交通工具讹躯,其類圖如下圖所示:

image.png

軟件設(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 方法流码。
  • 每種形狀(如 CircleRectangle)實(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 定義

里氏替換原則:如果 ST 的子類型,那么在程序中可以用 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)物的行為借嗽,包括 flyswim 方法。如果一個(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肌索、FlyableSwimmable 接口被拆分蕉拢,包含各自相關(guān)的方法。
  • Dog 類只實(shí)現(xiàn) Walkable 接口诚亚,而 Bird 類實(shí)現(xiàn) WalkableFlyable 接口晕换。

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 類直接訪問 CustomerAddress 對象并進(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 類直接依賴于 CustomerAddress 的實(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)建 FlyingBirdNonFlyingBird 類寡润,這可能會(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è)例子中舅柜,FlyingBirdNonFlyingBird 是通過繼承 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)中:

  • FlyableSwimmable 接口定義了不同的行為。
  • Bird 類依然提供基本功能氮块。
  • FlyingBirdSwimmingBird 類通過實(shí)現(xiàn)接口組合了不同的能力绍载。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市雇锡,隨后出現(xiàn)的幾起案子逛钻,更是在濱河造成了極大的恐慌,老刑警劉巖锰提,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件曙痘,死亡現(xiàn)場離奇詭異,居然都是意外死亡立肘,警方通過查閱死者的電腦和手機(jī)边坤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谅年,“玉大人茧痒,你說我怎么就攤上這事∪邗澹” “怎么了旺订?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長超燃。 經(jīng)常有香客問我区拳,道長,這世上最難降的妖魔是什么意乓? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任樱调,我火速辦了婚禮,結(jié)果婚禮上届良,老公的妹妹穿的比我還像新娘啊奄。我一直安慰自己紊浩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布沪么。 她就那樣靜靜地躺著械蹋,像睡著了一般检激。 火紅的嫁衣襯著肌膚如雪齐莲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天放祟,我揣著相機(jī)與錄音鳍怨,去河邊找鬼呻右。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鞋喇,可吹牛的內(nèi)容都是我干的声滥。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼侦香,長吁一口氣:“原來是場噩夢啊……” “哼落塑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起罐韩,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤憾赁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后散吵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體龙考,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年矾睦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了晦款。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡枚冗,死狀恐怖缓溅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赁温,我是刑警寧澤坛怪,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站股囊,受9級特大地震影響袜匿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜毁涉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一沉帮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贫堰,春花似錦穆壕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至偎行,卻和暖如春川背,著一層夾襖步出監(jiān)牢的瞬間贰拿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工熄云, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留膨更,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓缴允,卻偏偏與公主長得像荚守,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子练般,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容