1. 程式人生 > 其它 >設計模式(2)— 結構型模式

設計模式(2)— 結構型模式

2. 結構型模式

  結構型模式描述如何將類或物件按某種佈局組成更大的結構。它分為類結構型模式和物件結構型模式,前者採用繼承機制來組織介面和類,後者採用組合或聚合來組合物件。

  由於組合關係或聚合關係比繼承關係耦合度低,滿足“合成複用原則”,所以物件結構型模式比類結構型模式具有更大的靈活性。

結構型模式分以下7種

  • 代理模式
  • 介面卡模式
  • 裝飾者模式
  • 橋接模式
  • 外觀模式
  • 組合模式
  • 享元模式

案例:https://github.com/Kaiyko/DesignPattern

1.1 代理模式

1. 概述

  由於某些原因需要給某物件提供一個代理以控制該物件的訪問。這時,訪問物件不適合或者不能直接引用目標物件,代理物件作為訪問物件和莫表物件之間的中介。

  Java中的代理按照代理類生成時機不同又分為靜態代理動態代理。靜態代理代理類在編譯器就生成,而動態代理代理類則是在Java執行時動態生成。動態代理又又JDK代理CGLib代理兩種

2. 結構

代理(Proxy)模式分三種角色:

  • 抽象主題類(Subject):通過介面或抽象類宣告真實主題和代理物件實現的業務方法。
  • 真實主題類(Real Subject):實現了抽象主題中的具體業務,是代理物件所代表的真實物件,是最終要引用的物件。
  • 代理類(Proxy):提供了與真實主題相同的介面,其內部含有對真實主題的引用,它可以訪問、控制或擴充套件真實主題的功能。

3. 靜態代理

//	賣火車票的介面
public interface SellTickets {
    void sell();
}

//	火車站類
public class TrainStation implements SellTickets {
    @Override
    public void sell() {
        System.out.println("火車站買票");
    }
}

//	代理售票點類
public class ProxyPoint implements SellTickets {
    private TrainStation station = new TrainStation();
    @Override
    public void sell() {
        System.out.println("代售點收取一點服務費用");
        station.sell();
    }
}

//	測試類
public class TestClient {
    public static void main(String[] args) {
        //  建立代售點物件
        ProxyPoint proxyPoint = new ProxyPoint();
        //  呼叫方法進行買票
        proxyPoint.sell();
    }
}

  訪問物件直接訪問的是ProxyPoint類物件,也就是ProxyPoint作為訪問物件和目標物件的中介。同時也對sell方法進行了增強(代理店收取一些服務費用)。

4. JDK動態代理

  Java中提供了一個動態代理類Proxy,Proxy並不是我們上述所說的代理物件的類,而是提供了一個建立代理物件的靜態方法(newProxyInstance方法)來獲取代理物件。

//	賣火車票的介面
public interface SellTickets {
    void sell();
}

//	火車站類
public class TrainStation implements SellTickets {
    @Override
    public void sell() {
        System.out.println("火車站買票");
    }
}

//	獲取代理物件的工廠類 代理類也實現了對應的介面
public class ProxyFactory {

    //  宣告目標物件
    private TrainStation station = new TrainStation();

    //  獲取代理物件的方法
    public SellTickets getProxyObject() {
        //  返回代理物件
        return  (SellTickets) Proxy.newProxyInstance(
                station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                (proxy, method, args) -> {
                    System.out.println("jdk代售點收取小費");
                    System.out.println("invoke方法");
                    return method.invoke(station, args);
                }
        );
    }
}

//	測試類
public class TestClient {
    public static void main(String[] args) {
        //  1、建立代理工廠物件
        ProxyFactory factory = new ProxyFactory();
        //  2、使用factory物件的方法獲取代理物件
        SellTickets proxyObject = factory.getProxyObject();
        //  3、呼叫賣票的方法
        proxyObject.sell();
    }
}

ProxyFactory不是代理模式中的代理類,而代理類是程式在執行過程中生成的類。

5. CGLIB動態代理

6. 三種代理的對比

  • JDK代理和CGLIB代理

    使用CGLIB實現動態代理,CGLIB底層採用ASM位元組碼生成框架,使用位元組碼技術生成代理類,在JDK1.6之前比使用Java反射效率高。唯一需要注意的是,CGLIB不能對宣告為final的類或者方法進行代理,因為CGLIB的原理是動態生成被代理類的子類。

    子啊JDK1.6、JDK1.7、JDK1.8逐步對JDK動態代理優化之後,在呼叫次數較少的情況下,JDK代理效率高於CGLIB代理效率,只有驚醒大量呼叫的時候,JDK1.6和JDK1.7比CGLIB代理效率低一點,但是到JDK1.8的時候,JDK代理效率高於CGLIB。所以有介面則使用JDK動態代理,如果沒有介面則使用CGLIB代理。

  • 動態代理和靜態代理

    動態代理與靜態代理相比較,最大的好處是介面中宣告的所有方法都被轉移到呼叫處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在介面方法數量比較多的時候,我們可以進行靈活處理,而不需要想靜態代理那樣每一個方法進行中轉。

    如果介面增加一個方法,靜態代理模式除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了程式碼維護的複雜度。而動態代理不會出現該問題。

7. 優缺點

優點:

  • 代理模式在客戶端與目標物件之間起到一箇中介作用和保護目標物件的作用;
  • 代理物件可以擴充套件目標物件的功能;
  • 代理模式能將客戶端與目標物件分離,在一定程度上降低了系統的耦合度

缺點:

  • 增加了系統的複雜度。

8. 使用場景

  • 遠端代理(Remote)

    本地服務通過網路請求遠端服務,為了實現本地到遠端的通訊,我們需要實現網路通訊,處理其中可能的異常。為良好的程式碼設計和可維護性,我們將網路通訊部分隱藏起來,只暴露給本地服務一個介面,通過該介面即可訪問遠端服務提供的功能,而不必過多關心通訊部分的細節。

  • 防火牆代理(Firewall)

    當你將瀏覽器配置成使用代理功能時,防火牆就將你的瀏覽器的請求轉給網際網路;當網際網路返回響應時,代理伺服器再把它轉給你的瀏覽器。

  • 保護代理(Protect or Access)

    控制對一個物件的訪問,如果需要,可以給不同的使用者提供不同級別的使用許可權。

1.2 介面卡模式

1. 概述

定義:將一個類的介面轉換成客戶希望的另一個介面,使得原本由於介面不相容而不能一起工作的那些類能一起工作。

介面卡模式分為類介面卡模式和物件是佩秋模式,前者類之間耦合度比後者高,且要求程式設計師瞭解現有元件庫中的相關元件的內容結構,所以應用相對較少些。

2. 結構

介面卡模式(Adapter)包含以下主要角色:

  • 目標介面(Target):當前系統業務所期待的介面,它可以是抽象類或介面。
  • 適配者類(Adaptee):它是被訪問和適配的現存元件庫中的元件介面。
  • 介面卡類(Adapter):它是一個轉換器,通過繼承或引用適配者的物件,把適配者介面轉換成目標介面,讓客戶按目標介面的格式訪問適配者。

3. 類介面卡模式

定義一個介面卡類來實現當前系統的業務介面,同時又繼承現有元件庫中已經存在的元件。

實現略

類介面卡模式違背了合成複用原則。類介面卡是客戶類有一個介面規範的情況下可用,反之不可用。

4. 物件介面卡模式

  物件介面卡模式可採用將現有元件庫中已經實現的元件引入介面卡類中,該類同時實現當前系統的業務介面

  還有一個介面卡模式是介面介面卡模式。當不希望實現一個介面中所有的方法時,可以建立一個抽象類Adapter,實現所有方法,而此時我們只需要繼承該抽象類即可

5. 應用場景

  • 以前開發的系統存在滿足新系統功能需求的類,但其介面同資訊同的介面不一致。
  • 使用第三方提供的元件,但元件介面定義和自己要求的介面定義不同。

6. 拓展

  JDK原始碼中Read(字元流)、InputStream(位元組流)的適配使用的就是InputStreamReader和OutputStreamWrite分別繼承自java.io包中的Reader,對其中的抽象的為實現的方法給出實現。

1.3 裝飾者模式

1. 概述

  在不改變現有物件結構的情況下,動態地給物件增加一些職責(即增加其額外功能)的模式。

2. 結構

裝飾(Decorator)模式中的角色:

  • 抽象構件角色(Component):定義一個抽象介面以規範準備接收附加職責的物件
  • 具體構件角色(Concrte Component):實現抽象構件,通過裝飾角色為其新增一些職責。
  • 抽象裝飾角色(Decorator):繼承或實現抽象構件,幷包含具體構件的例項,可以通過其子類擴充套件具體構件的功能。
  • 具體裝飾角色(Concrete Decorator):實現抽線裝飾的相關方法,並給具體構件物件新增附加的責任。

3. 案例

好處:

  • 裝飾者模式可以帶來比繼承更加靈活的擴充套件功能,使用更加方便,可以通過組合不同的裝飾者物件來獲取具有不同行為狀態的多樣化的結果。裝飾者模式比繼承具有更良好的擴充套件性,完美的遵循開閉原則,繼承是靜態的附加職責,裝飾則是動態的附加職責。
  • 裝飾類和被裝飾類可以獨立發展,不會互相耦合,裝飾者模式是繼承的一個替代模式,裝飾模式可以動態擴充套件一個實現類的功能。

4. 使用場景

  • 當不能採用繼承的方式對系統進行擴充或者採用繼承不利於系統擴充套件和維護時。

    不能採用繼承的情況主要有兩類:

    • 第一類是系統中存在大量獨立的擴充套件,為支援每一種組合將產生大量的子類,使得子類數目呈爆炸性增長;
    • 第二類是因為類自定義不能繼承(如final類)
  • 在不影響其他物件的情況下,以動態、透明的方式給耽擱物件新增職責。

  • 當物件的功能要求可以動態地新增,也可以再動態地撤銷時。

5. 代理和裝飾者的區別

靜態代理和裝飾者模式的區別:

  • 相同點:

    • 都要實現和目標類相同的業務介面
    • 在兩個類中都要宣告目標物件
    • 都可以在不修改目標類的前提下增強目標的方法
  • 不同點:

    • 目的不同

      裝飾者是為了增強目標物件

      靜態代理是為了保護和隱藏目標物件

    • 獲取目標物件構件的地方不同

      裝飾者是由外界傳遞進來,可以通過構造方法傳遞

      靜態代理是在代理類內部建立,以此來隱藏目標物件

1.4 橋接模式

1. 概述

  將抽象與實現分離,使它們可以獨立變化。它是用組合關係代替繼承關係來實現,從而降低了抽象和實現這兩個可變維度的耦合度。

2. 結構

橋接模式(Bridge)包含以下主要角色:

  • 抽象化角色(Abstraction):定義抽象類,幷包含一個對實現化物件的引用。
  • 擴充套件抽象化角色(Refined Abstraction):是抽象化角色的子類,實現父類中的業務方法,並通過組合關係調用實現化角色的業務方法。
  • 實現化角色(Implementor):定義實現化角色的介面,供擴充套件抽象化角色呼叫。
  • 具體實現化角色(Concrete Implementor):給出實現化角色的具體實現。

3. 案例

好處:

  • 橋接模式提高了系統的可擴充性,在兩個變化維度中任意擴充套件一個維度,都不需要修改原有系統。
  • 實現細節對客戶透明

4. 使用場景

  • 當一個類存在兩個獨立變化的維度,且這兩個維度都需要進行擴充套件時。

  • 當一個系統不希望繼續使用繼承或因為多層次繼承導致系統類的個數急劇增加時。

  • 當一個系統需要在構件的抽象化角色和具體化角色之間增加更多的靈活性時。避免在兩個層次之間建立靜態的繼承聯絡,通過橋接模式可以使它們在抽象層建立一個關聯關係。

1.5 外觀模式

1. 概述

  外觀模式又名門面模式,是一種通過多個複雜子系統提供一個一致的介面,而使這些子系統更加容易被訪問的模式。該模式對外有一個統一介面,外部應用程式不用關心內部子系統的具體細節,這樣會大大降低應用程式的複雜度,提高了程式的可維護性。

2. 結構

外觀模式(Facade)包含以下主要角色:

  • 外觀角色(Facade):為多個子系統對外提供一個共同的介面。
  • 子系統角色(Sub System):實現系統的部分功能,客戶可以通過外觀角色訪問它。

3. 案例

4. 優缺點

優點:

  • 降低了子系統與客戶端之間的耦合度,使得子系統的變化不會影響呼叫它的客戶類。
  • 對客戶遮蔽了子系統元件,減少了客戶處理的物件數目,並使得子系統使用起來更加容易

缺點:

  • 不符合開閉原則,修改很麻煩

5. 使用場景

  • 對分層結構系統構建時,使用外觀模式定義子系統中每層的入口點可以簡化子系統之間的依賴關係。

  • 當一個複雜系統的子系統很多時,外觀模式可以為系統設計一個簡單的介面供外界訪問。

  • 當客戶端與多個子系統之間存在很大的聯絡時,引入外觀模式可將它們分離,從而提高子系統的獨立性和可移植性。

1.6 組合模式

1. 概述

  組合模式又名部分整體模式,是用於把一組相似的物件當作一個單一的物件。組合模式依據樹形接哦蠱來組合物件,用來表示部分以及整體層次。這種型別的設計模式屬於結構型模式,它建立了物件組的樹形結構。

2. 結構

組合模式主要包含三種角色:

  • 抽象根節點(Component):定義系統各層次物件的共有方法和屬性,可以預先定義一些預設行為和屬性。
  • 樹枝節點(Composite):定義樹枝節點的行為,儲存位元組點,組合樹枝節點和葉子節點形成一個樹形結構。
  • 葉子節點(Leaf):葉子節點,其下再無分支,是系統層次遍歷的最小單位。

3. 案例

4. 組合模式的分類

在使用組合模式時,根據抽象構件類的定義形式,我們可將組合模式分為透明組合模式和安全組合模式兩種形式。

  • 透明組合模式

    透明組合模式中,抽象根節點角色中聲明瞭所有用於管理成員物件的方法。這樣可以確保所有的構件類都有相同的介面。透明組合模式也是組合模式的標準形式。

    透明組合模式的缺點是不夠安全,因為葉子物件和容器物件在本質上是有區別的,葉子物件不可能有下一個層次的物件,即不可能包含成員物件,因此為其提供相關方法是沒有意義的,這在編譯階段不會出錯,但在執行階段如果呼叫這些方法可能出錯(如果沒有提供相應的錯誤處理程式碼)

  • 安全組合模式

    在安全組合模式中,在抽象構件角色中沒有宣告任何用於管理成員物件的方法,而是在樹枝節點中宣告並實現這些方法。安全組合模式的缺點是不夠透明,因為葉子構件和容器構件具有不同的方法,且容器構件中那些勇於管理成員物件的方法沒有在抽象構件類中定義,因此客戶端不能完全針對抽象變成,必須有區別地對待葉子構件和容器構件。

5. 優點

  • 組合模式可以清楚地定義分層次的複雜物件,表示物件的全部或部分層次,它讓客戶端忽略了層次的差異,方便對整個層次結構進行控制。
  • 客戶端可以一致地使用一個組合結構或其中單個物件,不必關心處理的是單個物件還是整個組合結構,簡化了客戶端程式碼。
  • 在組合模式中增加的新的樹枝節點和葉子節點都很方便,無需對現有類庫進行任何修改,符合“開閉原則”。
  • 組合模式為屬性結構的面向物件實現提供了一種靈活的解決方案,通過葉子節點和樹枝節點的遞迴組合,可以形成複雜的樹形接哦股,但對樹形結構的控制卻非常簡單。

6. 使用場景

組合模式正式應樹形結構而生,所以組合模式的使用場景就是出現樹形接哦股的地方。比如:檔案目錄的顯示,多級目錄呈現等樹形結構資料的操作。

1.7 享元模式

1. 概述

  運用共享技術來有效地支援大量細粒度物件的服用。它通過共享已經存在的物件來大幅度減少需要建立的物件數量、避免大量相似物件的開銷,從而提高系統資源的利用率。

2. 結構

享元模式(Flyweight)存在以下兩種狀態:

  • 內部狀態,即不會隨著環境的改變而改變的可共享部分。
  • 外部狀態,指隨環境改變改變的不可共享的部分。

享元模式的實現要領就是區分應用中的這兩種狀態,並將外部狀態外部化。

享元模式主要有以下角色:

  • 抽象享元角色(Flyweight):通常是一個介面或抽象類,在抽象享元類中聲明瞭具體享元類公共的方法,這些方法可以向外界提供享元物件的內部資料(內部狀態),同事也可以通過這些方法來設定外部資料(外部狀態)。
  • 具體享元角色(COncrete Flyweight):它實現了抽象享元類,稱為享元物件;在具體享元類中為內部狀態提供了儲存空間。通常我們可以結合單例模式來設計具體享元類,為每一個具體享元類提供唯一的享元物件。
  • 非享元角色(Unsharable Flyweight):並不是所有的抽象享元類的子類都需要被共享,不能被共享的子類可設計為非共享具體享元類;當需要一個非共享具體享元類的物件可以直接通過例項化建立。

3. 案例

4. 優缺點

優點:

  • 極大減少記憶體中相似或相同的物件數量,節約系統資源,提供系統性能
  • 享元模式中的外部狀態相對獨立,且不影響內部狀態。

缺點:

為了使物件可以共享,需要將享元物件的部分狀態外部化,分離內部狀態和外部狀態,使程式邏輯複雜

5.使用場景

  • 一個系統有大量相同或者相似的對線,造成記憶體的大量耗費。

  • 物件的大部分狀態都可以外部化,可以將這些外部狀態傳入物件中。

  • 在使用享元模式時需要維護一個儲存物件的享元池,而者需要耗費一定的系統資源,因此,應當在需要多次重複使用享元物件時才值得使用享元模式。