七大原則+23種設計模式


菜鳥教程更香

設計模式的意義

編寫軟件過程中,程序員面臨着來自耦合性,內聚性以及可維護性,可擴展性,複用性,靈活性等多方面的挑戰,設計模式是爲了讓程序(軟件),具有更好的

  • 代碼複用性
  • 可讀性
  • 可擴展性
  • 可靠性 (當我們增加新的功能後,對原來的功能沒有影響)
  • 使程序呈現高內聚,低耦合的特性

1. 七大原則

設計模式原則,其實就是程序員在編程時,應當遵守的原則,也是各種設計模式的基礎

設計模式常用的七大原則有:

  • 單一職責原則
  • 接口隔離原則
  • 依賴倒轉(倒置)原則
  • 里氏替換原則
  • 開閉原則
  • 迪米特法則
  • 合成複用原則

1.1 單一職責原則

大白話解釋:一個類就是一個最小的功能單位

描述
一個類應該只負責一項職責
如類A負責兩個不同職責:職責1,職責2。 當職責1需求變更而改變A時,可能造成職責2執行錯誤,所以需要將類A的粒度分解爲 A1,A2。

實例
假設有一個“交通工具”類,他的作用只有一個,就是“運行交通工具”,假設它只有一個run方法,打印“交通工具 xx 在地上跑”這句話。

如果我們的交通工具只是車,這個類沒有問題,如果交通工具加上“飛機”、“船”,那麼“交通工具 飛機 在地上跑”、“交通工具 船 在地上跑”就不符合實際。

由於交通工具有多個,因此這個類不符合“單一職責原則”。
我們可以將其改爲3個類,“水上交通工具”、“空中交通工具”、“陸地交通工具”,分別對海陸空負責(單一職責)。

此外,由於這個類的功能比較單一,只有run方法,我們也可以在方法級別上實現單一職責原則,即爲該類創建“水上運行”、“空中運行”、“陸地運行”方法。

注意事項與細節

  • 降低類的複雜度,一個類只負責一項職責。
  • 提高類的可讀性,可維護性
  • 降低變更引起的風險
  • 通常情況下,我們應當遵守單一職責原則,只有邏輯足夠簡單,纔可以在代碼級違 反單一職責原則;如果類中方法數量足夠少,可以在方法級別保持單一職責原則

1.2 接口隔離原則

大白話解釋:實現接口的所有類都應當覺得接口中沒有多餘的方法

Interface Segregation Principle
描述
客戶端不應該依賴它不需要的接口,即一個類對另一個類的依賴應該建立在最小的接口上。

實例
在這裏插入圖片描述
A通過調用B,需要操作1、2、3,
C通過調用D,需要操作1、4、5,
但是B、D都實現了1、2、3、4、5,顯然B、D都實現了多餘的方法。

在這裏插入圖片描述
若要符合“接口隔離原則”,只需要讓B、D實現必需的接口即可。

然而,實際中我們不一定能確定B是否真的不需要4、5方法,也不能確定D是否真的不需要2、3方法。
因此,不是說學好設計模式就萬事大吉的。
實際還得多方面考慮。

1.3 依賴倒轉(倒置)原則

大白話解釋:我們都是用接口聲明一個變量,而不是直接使用具體類(如聲明一個ArrayList,最左邊用的是List接口;聲明一個HashMap,最左邊用的是Map)

Dependence Inversion Principle
描述

  • 高層模塊不應該依賴低層模塊,二者都應該依賴其抽象
  • 抽象不應該依賴細節,細節應該依賴抽象
  • 依賴倒轉(倒置)的中心思想是面向接口編程
  • 依賴倒轉原則是基於這樣的設計理念:相對於細節的多變性,抽象的東西要穩定的多。以抽象爲基礎搭建的架構比以細節爲基礎的架構要穩定的多。(在java中,抽象指的是接口或抽象類,細節就是具體的實現類)
  • 使用接口或抽象類的目的是制定好規範,而不涉及任何具體的操作,把展現細節的任務交給他們的實現類去完成

就是一句話,前期設計應當從最基本、最核心的抽象入手,構建接口。

實例
用戶(User類)通過receive方法接收信息(Message類)。
如果用戶接受的消息包括Email、QQ、WeChat…等多種方式,那麼我們就需要在User類中寫多個receive重載函數分別接收不同的消息類。
然而,誰也不知道以後還會有什麼消息類,這就導致每次增加一個消息類,我們都得對User類進行修改。

如果Message不是類,而是一個接口,receive接收的是Message接口,就能解決問題。
只需要讓各種不同的消息類實現Message接口,receive就能夠接收他們;如果出現新的消息類,也只需要增加該消息類並實現Message接口即可,不需要對原有代碼進行更改。

依賴關係傳遞的方式

  • 聲明接口傳遞:在普通方法參數中聲明接口
  • 構造方法傳遞:在構造方法參數中聲明接口
  • setter方法傳遞:類中有個接口成員,該成員通過setter聲明

注意事項和細節

  • 低層模塊儘量都要有抽象類或接口,或者兩者都有,程序穩定性更好
  • 變量的聲明類型儘量是抽象類或接口, 這樣我們的變量引用和實際對象間,就存在 一個緩衝層,利於程序擴展和優化
  • 繼承時遵循里氏替換原則

1.4 里氏替換原則

大白話解釋:少繼承、別重寫父類方法

OO中的繼承,產生的問題

  • 繼承包含這樣一層含義:父類中凡是已經實現好的方法,實際上是在設定規範和契約,雖然它不強制要求所有的子類必須遵循這些契約,但是如果子類對這些已經實現的方法任意修改,就會對整個繼承體系造成破壞。
  • 繼承在給程序設計帶來便利的同時,也帶來了弊端。比如使用繼承會給程序帶來侵入性,程序的可移植性降低,增加對象間的耦合性,如果一個類被其他的類所繼承, 則當這個類需要修改時,必須考慮到所有的子類,並且父類修改後,所有涉及到子類的功能都有可能產生故障

解決以上問題,考慮里氏替換原則。

描述
Liskov Substitution Principle

  • 如果對每個類型爲T1的對象o1,都有類型爲T2的對象o2,使得以T1定義的所有程序P在所有的對象o1都代換成o2時,程序P的行爲沒有發生變化,那麼類型T2是類型T1 的子類型。換句話說,所有引用基類的地方必須能透明地使用其子類的對象
  • 在使用繼承時,遵循里氏替換原則,在子類中儘量不要重寫父類的方法
  • 里氏替換原則告訴我們,繼承實際上讓兩個類耦合性增強了,在適當的情況下,可以通過聚合組合依賴 來解決問題。.

實例
類A的fun1是減法器,類B繼承了類A;但是類B不小心將fun1重寫成了加法器。
假設極端情況,類A就只有fun1方法,那麼類B繼承類A就沒有必要了,把A唯一的方法都重寫了。

實際編程中常常重寫父類的方法,但是整個繼承體系的複用性、穩定性較差。
一般可以這麼做:讓原來的父類A和子類B都繼承一個更通俗的基類,取消AB繼承關係,AB直接採用聚合、組合、依賴的關係實現方法調用。

比如上述實例,A和B都繼承一個基礎類Base(爲了保證一些基本方法),然後B中聲明一個成員類A,此時B可以寫一個方法調用A的專有方法即可。

1.5 開閉原則

大白話解釋:寫的系統易於擴展,不允許修改。

Open Closed Principle
描述

  • 開閉原則是編程中最基礎、最重要的設計原則
  • 一個軟件實體如類,模塊和函數應該對擴展開放,對修改關閉用抽象構建框架,用實現擴展細節
  • 當軟件需要變化時,儘量通過擴展軟件實體的行爲來實現變化,而不是通過修改已有的代碼來實現變化。
  • 編程中遵循其它原則,以及使用設計模式的目的就是遵循開閉原則

1.6 迪米特法則

大白話解釋:減少類之間的依賴

描述

  • 一個對象應該對其他對象保持最少的瞭解
  • 類與類關係越密切,耦合度越大

迪米特法則(Demeter Principle)又叫最少知道原則,即一個類對自己依賴的類知道的越少越好。也就是說,對於被依賴的類不管多麼複雜,都儘量將邏輯封裝在類的內部對外除了提供的public 方法,不泄露任何信息

迪米特法則還有個更簡單的定義:只與直接的朋友通信

直接的朋友:每個對象都會與其他對象有耦合關係,只要兩個對象之間有耦合關係, 我們就說這兩個對象之間是朋友關係。
耦合的方式很多,依賴,關聯,組合,聚合 等。其中,我們稱出現成員變量方法參數方法返回值的類爲直接的朋友,而出現在局部變量中的類不是直接的朋友。也就是說,陌生的類最好不要以局部變量的形式出現在類的內部

此外,有時候我們也會引入了“陌生的朋友”而不自知。
比如較長的調用鏈,調用鏈中可能生成了多個陌生的類,這也是不被允許的。

迪米特法則的目的在於降低類之間的耦合度。

1.7 合成複用原則

大白話解釋:多用組合、聚合,少用繼承

描述
儘量使用合成/聚合的方式,而不是使用繼承。

如果僅僅是爲了讓B類使用A類的方法,就讓B繼承A,只是徒增耦合。
我們只需要在B中聚合一個A的對象,或者將A作爲B的某個方法參數。

小結

  • 找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混在一起。
  • 針對接口編程,而不是針對實現編程。
  • 爲了交互對象之間的松耦合設計而努力

2. UML類圖

點擊此處進行學習

3. 設計模式

設計模式分爲三種類型,共23種

  • 創建型模式:單例模式、工廠模式、抽象工廠模式、原型模式、建造者模式。
  • 結構型模式:適配器模式、橋接模式、裝飾模式、組合模式、外觀模式、享元模式、代理模式。
  • 行爲型模式:模板方法模式、命令模式、訪問者模式、迭代器模式、觀察者 模式、中介者模式、備忘錄模式、解釋器模式、狀態模式、策略模式、職責鏈模式(責任鏈模式)。

大白話解釋:
創建型模式:

  • 單例模式:創建一個對象,使得整個系統中,這個對象有且僅有一個。
  • 工廠模式:多個類的創建交給一個工廠,客戶端給出所需的類的類型,工廠返回該對象
  • 抽象工廠模式:給每一類產品都創建一個工廠
  • 原型模式:利用一個對象本體(稱爲原型),克隆出另一個
  • 建造者模式:一個對象的創建過程比較複雜,通過一系列建造過程完成創建

結構型模式:

  • 適配器模式:將類通過一個“轉換器”轉換某些屬性讓它變得適合使用
  • 橋接模式:將類的某一可變屬性抽出來作爲類組合進去(幾何圖形有三種顏色,將三種顏色抽取出來作爲三個類)
  • 裝飾模式:把類放進去加工一下,得到具有額外功能的類
  • 組合模式:具有樹形結構的類羣,使用組合模式讓其變成一個樹
  • 外觀模式:一大堆類的運行放在一個類中(外觀),一鍵調用
  • 享元模式:實現元素共享
  • 代理模式:爲了控制對象的訪問,加一個代理人

行爲型模式:

  • 模板方法模式:把執行流程(算法)都放在一個方法中,形成一個模板
  • 命令模式:將所有命令單獨抽出來作爲類來調用,而不是作爲方法
  • 訪問者模式:A進入B中(B某個方法使用了A),調用A的方法訪問B(A的方法中又需要傳入B)
  • 迭代器模式:遍歷類
  • 觀察者模式:一對多關係時,這個一想要通知多個對象
  • 中介者模式:讓客戶與多個類的溝通,都通過一箇中介類
  • 備忘錄模式:記錄一個類的狀態
  • 解釋器模式:解釋一串表達式
  • 狀態模式:一個流程存在特別多的狀態,使用狀態模式
  • 策略模式:把類的某一類方法(策略)抽出來作爲類,實現動態改變
  • 職責鏈模式(責任鏈模式):攔截鏈,輪着一個攔一個

3.1單例模式

單例模式有八種實現方法(有一種是錯誤示範):

  • 餓漢式(靜態常量)
  • 餓漢式(靜態代碼塊)
  • 懶漢式(線程不安全)
  • 懶漢式(線程安全,同步方法)
  • 懶漢式(線程安全,同步代碼塊)
  • 雙重檢查
  • 靜態內部類
  • 枚舉

代碼以及註釋詳解

3.2抽象工廠模式

簡單工廠模式
在這裏插入圖片描述
AbstractProduct:抽象類產品,比如說披薩
ConcreteProduct:具體的產品,比如說中國披薩,美國披薩,巴西披薩等
SimpleFactory:用於創建披薩類,依賴於抽象披薩
FacrotyClient:工廠的使用者,通過FactoryClient,調用SimpleFactory生成不同的披薩。

所有代碼以及解釋

3.3原型模式

原型模式(Prototype Pattern)是用於創建重複的對象,同時又能保證性能。
Spring的bean.xml中配置的bean,scope可以選擇單例,也可以選擇prototype,即原型模式創建。
如果你的bean中有初始化信息,那麼通過prototype模式創建的bean,都會帶上這些初始化信息

原理:實現Cloneable接口,利用Object的clone,實現克隆

淺拷貝:使用clone克隆的對象,基本類型是值傳遞(新的對象),引用類型只是地址傳遞(依舊是指向舊的對象,如果此時對該對象修改,也會影響原有對象)。

深拷貝:
對於拷貝對象中的引用類型,也實現Cloneable接口,然後對其單獨處理。但是這種方式侷限性太大,不推薦,直接學習另一種方式,利用反序列化

原理:序列化之後,能夠保存所有“值信息”
反序列化得到的是一個新的對象。

public class PrototypePattern {
    public static void main(String[] args) {
        Sheep s1 = new Sheep("duoli","black");
        Sheep friend = new Sheep("friend","red");
        s1.setFriend(friend);
        Sheep s2 = (Sheep) s1.deepClone();

        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s1.getFriend()==s2.getFriend());
    }
}

class Sheep implements Serializable {
    private String name;
    private String color;

    // 深拷貝測試對象
    private Sheep friend;

    Sheep(String name,String color){
        this.name=name;
        this.color=color;
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                ", friend=" + friend +
                '}';
    }

    Object deepClone() {

        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        ByteArrayInputStream bis = null;


        try {
            // 序列化
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            // 反序列化
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            return (Sheep) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }finally {
            try {
                if (bos != null) {
                    bos.close();
                }
                if (oos != null) {
                    oos.close();
                }
                if (bis != null) {
                    bis.close();
                }
                if (ois != null) {
                    ois.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    public void setFriend(Sheep friend) {
        this.friend = friend;
    }

    public Sheep getFriend() {
        return friend;
    }
}
  1. 創建新的對象比較複雜時,可以利用原型模式簡化對象的創建過程,同時也能夠提高效率
  2. 不用重新初始化對象,而是動態地獲得對象運行時的狀態
  3. 如果原始對象發生變化(增加或者減少屬性),其它克隆對象的也會發生相應的變化,無需修改代碼
  4. 在實現深克隆的時候可能需要比較複雜的代碼
  5. 缺點:需要爲每一個類配備一個克隆方法,這對全新的類來說不是很難,但對已有的類進行改造時,需要修改其源代碼,違背了ocp原則

3.4建造者模式

BuilderPattern
蓋房子,需要逐步完成一系列工序才能得到最終的房子。
建造者模式,就是將產品(房子)與產品構造過程(一系列工序)進行解耦。

涉及角色:
產品:Product,比如一棟房子
抽象建造者:Builder,大家一直認爲施工隊該有的基本操作
具體建造者:ConcreteBuilder,實際開工的工人,只提供動作,指揮者讓幹嘛就幹嘛
指揮者:Director,工頭(隔離了老闆與工人直接接觸,負責控制房子的生產過程)

調用過程:
(客戶端)老闆告訴(指揮者)工頭,我要“這種”房子(指定建造者,
工人),工頭對這類房子的建造方式已經記住了,然後告訴工人開始搭建,最後工頭將房子交給老闆。

此處老闆指定建造者,而不是老闆定製(指定)房子,是因爲:
如果定製房子,老闆就得對房子的參數非常熟悉纔行
如果有新的建造者加入,就得更改客戶端代碼

package creationMode._4_builderPattern;

public class BuilderPattern {
    public static void main(String[] args) {
        // 老闆跟包工頭說要一個小平房
        Director director = new Director(new ConcreteHouseBuilderOne());
        House result1 = director.getResult();
        System.out.println(result1);

        // 老闆跟包工頭說要一棟高樓
        director = new Director(new ConcreteHouseBuilderTwo());
        House result2 = director.getResult();
        System.out.println(result2);

    }
}

// 產品:房子
class House{
    private String name;
    private Integer height;
    House(){}

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getHeight() {
        return height;
    }

    public void setHeight(Integer height) {
        this.height = height;
    }

    @Override
    public String toString() {
        return "House{" +
                "name='" + name + '\'' +
                ", height=" + height +
                '}';
    }
}

// 抽象建造者
interface AbstractHouseBuilder{
    void buildStepOne();
    void buildStepTwo();
    House getResule();
}

// 具體建造者:工人1號
class ConcreteHouseBuilderOne implements AbstractHouseBuilder{
    private House house = new House();
    @Override
    public void buildStepOne() {
        this.house.setName("小平房");
    }
    @Override
    public void buildStepTwo() {
        this.house.setHeight(10);
    }

    @Override
    public House getResule() {
        return house;
    }
}

// 具體建造者:工人2號
class ConcreteHouseBuilderTwo implements AbstractHouseBuilder{
    private House house = new House();
    @Override
    public void buildStepOne() {
        this.house.setName("大高樓");
    }
    @Override
    public void buildStepTwo() {
        this.house.setHeight(100);
    }

    @Override
    public House getResule() {
        return house;
    }
}

// 指揮者:包工頭
class Director{
    private AbstractHouseBuilder builderOne;

    Director(AbstractHouseBuilder builderOne){
        this.builderOne = builderOne;
    }

    House getResult(){
        builderOne.buildStepOne();
        builderOne.buildStepTwo();
        return builderOne.getResule();
    }
}

StringBuilder使用的建造者模式

  • Appendable 接口定義了多個append方法(抽象方法), 即Appendable 爲抽象建造者, 定義了抽象方法
  • AbstractStringBuilder 實現了 Appendable 接口方法,這裏的AbstractStringBuilder 已經是建造者,只是不能實例化
  • StringBuilder 即充當了指揮者角色,同時充當了具體的建造者,建造方法的 實現是由 AbstractStringBuilder 完成, 而StringBuilder 繼承了 AbstractStringBuilder

特點

  • 客戶端(使用程序)不必知道產品內部組成的細節,將產品本身與產品的創建過程解耦,使得相同的創建過程可以創建不同的產品對象
  • 每一個具體建造者都相對獨立,而與其他的具體建造者無關,因此可以很方便地替換具體建造者或增加新的具體建造者, 用戶使用不同的具體建造者即可得到不同 的產品對象
  • 可以更加精細地控制產品的創建過程 。將複雜產品的創建步驟分解在不同的方法中,使得創建過程更加清晰,也更方便使用程序來控制創建過程
  • 增加新的具體建造者無須修改原有類庫的代碼,指揮者類針對抽象建造者類編程, 系統擴展方便,符合 “開閉原則”
  • 建造者模式所創建的產品一般具有較多的共同點,其組成部分相似,如果產品之間的差異性很大,則不適合使用建造者模式,因此其使用範圍受到一定的限制。
  • 如果產品的內部變化複雜,可能會導致需要定義很多具體建造者類來實現這種變化, 導致系統變得很龐大,因此在這種情況下,要考慮是否選擇建造者模式.
  • 抽象工廠模式VS建造者模式:抽象工廠模式實現對產品家族的創建,一個產品家族是這樣的一系列產品:具有不同分類維度的產品組合,採用抽象工廠模式不需要關心構建過程,只關心什麼產品由什麼工廠生產即可。而建造者模式則是要求按照指定的藍圖建造產品,它的主要目的是通過完成一系列工序而產生一個新產品

3.5適配器模式

描述

  1. 適配器模式(Adapter Pattern)將某個類的接口轉換成客戶端期望的另一個接口表示,主的目的是兼容性,讓原本因接口不匹配不能一起工作的兩個類可以協同工作。其別名爲包裝器(Wrapper)
  2. 適配器模式屬於結構型模式
  3. 主要分爲三類:類適配器模式對象適配器模式接口適配器模式

工作原理

  1. 適配器模式:將一個類的接口轉換成另一種接口.讓原本接口不兼容的類可以兼 容
  2. 從用戶的角度看不到被適配者,是解耦的
  3. 用戶調用適配器轉化出來的目標接口方法,適配器再調用被適配者的相關接口方法
  4. 用戶收到反饋結果,感覺只是和目標接口交互,如圖
    在這裏插入圖片描述

類適配器

Adapter類,通過繼承 src類,實現 dst 類接口,完成src->dst的適配。

實例
在這裏插入圖片描述

國家只提供了220V電壓,我們的手機需要5V電壓,因此需要一個適配器。

適配器繼承220V電壓(爲了拿到電壓),然後實現5V的接口(相當於適配器變壓標準)。
接着手機充電的時候,只需要遵循5V接口就行了。
然後用戶Client左拿適配器(充電器),右拿手機,就可以充電了。

注意事項

  1. Java是單繼承機制,所以類適配器需要繼承src類這一點算是一個缺點, 因爲這要求dst必須是接口,有一定侷限性;
  2. src類的方法在Adapter中都會暴露出來,也增加了使用的成本。
  3. 由於其繼承了src類,所以它可以根據需求重寫src類的方法,使得Adapter的靈活性增強了。

對象適配器

Adapter類,通過持有 src類,實現 dst 類接口, 完成src->dst的適配。

細節

  1. 對象適配器和類適配器其實算是同一種思想,只不過實現方式不同。 根據合成複用原則,使用組合替代繼承, 所以它解決了類適配器必須繼承src的 侷限性問題,也不再要求dst必須是接口。
  2. 使用成本更低,更靈活。

接口適配器

  1. 一些書籍稱爲:適配器模式(Default Adapter Pattern)或缺省適配器模式。
  2. 當不需要全部實現接口提供的方法時,可先設計一個抽象類實現接口,併爲該接口中每個方法提供一個默認實現(空方法),那麼該抽象類的子類可有選擇地覆蓋父類的某些方法來實現需求
  3. 適用於一個接口不想使用其所有的方法的情況。

實例
在這裏插入圖片描述

如上圖,接口提供了4個方法,如果我們直接實現接口,將不得不實現全部方法。
但是我們加一個適配器,實現接口中的全部方法(但是都是空方法,沒有寫具體實現),
然後A繼承適配器,就可以有選擇的重寫自己想要的方法。

Spring源碼適配器模式分析

在這裏插入圖片描述

實現邏輯:前端發起請求,DispatchServlet調用doDispatch()方法,調用controller方法。

爲什麼應用適配器模式:前端發起的請求有多種,可能需要HttpController進行處理,也可能是SimpleController或者AnnotationController進行處理,但是我們並不知道要用哪個;最簡單的方式就是在處理的時候用if-else對請求類別進行甄別,然後調用對應controller;但是,顯然這種模式違背了OCP原則。

適配器模式:
處理請求的控制器接口;
分別處理請求的三個具體控制器;
每個具體的控制器都有一個對應的適配器,這些適配器都遵循同一個適配器接口。
dispatchServlet拿到請求之後,通過適配器判斷控制器類型,並調用控制器方法。

模擬運行軌跡:
前端發起Http請求==》DispatcherServlet拿到請求,調用doDispatch()
》doDispatch()拿到該請求,交給HandlerAdapter適配器進行類別判斷》判斷結果爲Http請求,此時用HttpHandlerAdapter調用HTTPController中的方法,處理請求。


適配器模式注意事項

  1. 三種命名方式,是根據 src是以怎樣的形式給到Adapter(在Adapter裏的形式)來命名的。
  2. 類適配器:以類給到,在Adapter裏,就是將src當做類,繼承
    對象適配器:以對象給到,在Adapter裏,將src作爲一個對象,持有
    接口適配器:以接口給到,在Adapter裏,將src作爲一個接口,實現
  3. Adapter模式最大的作用還是將原本不兼容的接口融合在一起工作。
  4. 實際開發中,實現起來不拘泥於上述三種經典形式

3.7橋接模式

考慮這麼一個問題:有“圓形”“正方形”“三角形”三個形狀,然後有“紅色”“綠色”“藍色”三種顏色,現在我們需要“紅色正方形”、“綠色正方形”、“藍色正方形”、“紅色三角形”、“綠色三角形”、“藍色三角形”、“紅色圓形”、“綠色圓形”、“藍色圓形”,需要幾個類?
如果是按x色x形分別建類,那麼顯然需要3*3個類。
這種實現快速簡單,但是卻難以擴展。比如我們現在多了一個綠色,那麼就得再建三個“綠色xx形”,形成類爆炸。

這就是理解橋接模式的方式——屬性維度。
將每一維,都單獨抽出來,然後通過組合聚合的形式放在一個橋接類中。

在這裏插入圖片描述
這個“抽象”與“實現”的區別,網上並沒有找到結論。
個人覺得,沒啥區別,抽象接口直接跟Client交互,並且組合實現類,因此,可以將重要的“屬性”作爲抽象。
或者說,抽象與實現是主從關係,實現屬於抽象,比如說“顏色屬於形狀,因此形狀是抽象,顏色是實現”。

此外,個人覺得這個二維維度可以擴展成多維。

3.8裝飾模式

定義
在不改變原有對象的基礎之上,將功能附加到對象上。提供了比繼承更有彈性的替代方案(擴展原有對象功能)

優點

  • 擴展一個類的功能或者給一個類添加附加職責
  • 給一個對象動態的添加功能,或動態撤銷功能。

在這裏插入圖片描述
類圖特點:

  • 被裝飾者和裝飾器都實現同一個接口(抽象類),該接口中有被裝飾者的可裝飾屬性相關方法

    比如說用調味品裝飾咖啡,那麼咖啡的描述和價格就是可裝飾屬性,此時該頂級接口應當帶上描述和價格,只有這樣,才能在裝飾後對這些屬性進行動態修改。

3.9組合模式

常見這種形式:

  • 一個用戶有多個訂單,一個訂單有多個訂單詳情,一個訂單詳情有多個商品
  • 一個學校有多個學院,一個學院有多個系,一個繫有多個班,一個班有多個同學

這種模式,可以抽象爲一棵樹
學校是一個根節點,同學是葉子節點。

這就是組合模式。

在這裏插入圖片描述

  • ClientApp是調用方,只跟Component發生關聯
  • Component是抽象節點,包含根節點和葉子節點的所有默認行爲
  • Composite是非葉子節點
  • Leaf是葉子節點。

注意:

  • Component作爲抽象類時,將葉子節點也包含的行爲作爲抽象方法,讓葉子也能實現;將葉子節點不包含的行爲(如移除子節點和增加子節點)寫一個默認實現,拋出不支持調用的異常,讓其他子節點自己實現。
  • 一般情況下,儘管都是非葉子節點,但是也可能實現不一致(比如增加一個學院和增加一個系,實際邏輯不一樣),因此不能只寫一個非葉子節點,可能存在多個很相似的非葉子節點

其他細節:

  • 簡化客戶端操作。客戶端只需要面對一致的對象而不用考慮整體部分或者節點葉子的問題。
  • 具有較強的擴展性。當我們要更改組合對象時,我們只需要調整內部的層次關係, 客戶端不用做出任何改動.
  • 方便創建出複雜的層次結構。客戶端不用理會組合裏面的組成細節,容易添加節點 或者葉子從而創建出複雜的樹形結構
  • 需要遍歷組織機構,或者處理的對象具有樹形結構時, 非常適合使用組合模式.
  • 要求較高的抽象性,如果節點和葉子有很多差異性的話,比如很多方法和屬性都不一樣,不適合使用組合模式

劃重點:樹形結構適合、能幫助客戶端忽略節點和葉子的差異性、如果節點和葉子本身存在較大差異則不適合使用組合模式(比如學校和學生的操作肯定存在較大差異,建議將學生、班級從這個樹狀結構排除,讓系作爲葉子節點)

3.10外觀模式

考慮:
家庭影院系統

  • 幕布的開關
  • 投影儀的開關,亮度調節,節目選擇
  • 躺椅的準備與收起

看電影的步驟:
準備躺椅
打開幕布
打開投影儀,調節亮度,選擇節目

結束看電影的步驟:
關閉投影儀
關閉幕布
收起躺椅

如果是用戶直接面對躺椅、幕布、投影儀,那他就得一步一步操作(儘管這裏看起來不復雜),假設是一個子系統非常多的大系統,那麼這些一步一步操作就會非常複雜。

可以這麼做:
弄一個Facade類,組合幕布、投影儀、躺椅,用戶只需要輸入一個電影名,這個影院系統就自動完成所有準備工作。

在這裏插入圖片描述
外觀模式的注意事項和細節

  • 外觀模式對外屏蔽了子系統的細節,因此外觀模式降低了客戶端對子系統使用的複雜性
  • 外觀模式對客戶端與子系統的耦合關係,讓子系統內部的模塊更易維護和擴展
  • 通過合理的使用外觀模式,可以幫我們更好的劃分訪問的層次
  • 當系統需要進行分層設計時,可以考慮使用Facade模式
  • 維護一個遺留的大型系統時,可能這個系統已經變得非常難以維護和擴展,此時可以考慮爲新系統開發一個Facade類,來提供遺留系統的比較清晰簡單的接口, 讓新系統與Facade類交互,提高複用性
  • 不能過多的或者不合理的使用外觀模式,使用外觀模式好,還是直接調用模塊好。 要以讓系統有層次,利於維護爲目的
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章