《設計模式》3.結構型模式


點擊進入個人博客

3.1 適配器模式

適配器模式把一個類的接口變換成客戶端所期待的另外一種接口,使得本來因接口不匹配而沒法在一塊兒工做的兩個類可以在一塊兒工做。java

3.1.1 類的適配器結構

類的適配器

  • 目標(Target)角色:這就是所期待獲得的接口,因爲是類適配器模式,所以目標不能夠是類。
  • 源(Adaptee)角色:現有須要適配的接口。
  • 適配器(Adapter)角色:適配器類是本模式的核心,必須是具體類。
interface Target {
    void operation1();
    void operation2();
}

class Adaptee {
    public void operation1() {}
}

class Adapter extends Adaptee implements Target {
    @Override
    public void operation2() {}
}
類的適配器
  • 類的適配器模式把被的類的API轉換成目標類的API。
  • 是經過繼承實現的。
類的適配器效果
  • 使用一個具體類把源適配到目標中。這樣若是源以及源的子類都使用此類適配,就行不通了。
  • 因爲適配器是源的子類,所以能夠在適配器中重寫源的一些方法。
  • 因爲引進了一個適配器類,所以只有一個線路到達目標類,是問題獲得簡化。

3.1.2 對象的適配器結構

對象適配器

  • 目標(Target)角色:這就是所期待獲得的接口,所以目標能夠是具體或抽象的類
  • 源(Adaptee)角色:現有須要適配的接口。
  • 適配器(Adapter)角色:適配器類是本模式的核心,必須是具體類。
interface Target {
    void operation1();
    void operation2();
}

class Adaptee {
    public void operation1() {}
}

class Adapter implements Target {
    private Adaptee adaptee;

    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void operation1() {
        adaptee.operation1();
    }

    @Override
    public void operation2() {
        // do something
    }
}
對象的適配器
  • 對象的適配器是經過依賴實現的
  • 推薦使用該方法
對象的適配器效果
  • 一個適配器能夠把多種不一樣的源適配到同一個目標,即同一個適配器能夠把源類和它的子類都適配到目標接口
  • 與類的適配器模式相比,要想置換源類的方法就不容易。
  • 增長新的方法方便的多

3.1.3 細節

使用場景
  1. 系統須要使用現有的類,而此類的接口不符合系統的須要
  2. 想要創建一個能夠重複使用的類,用於與一些彼此間沒有太大關聯的一些類,包括一些可能在未來引進的類一塊兒工做。
  3. 對對象的適配器兒模式而言,在設計裏,須要改變多個已有的子類的接口,若是使用類的適配器模式,就須要針對每一個子類作一個適配器類,這不太實際。
優勢
  1. 可讓任何兩個沒有關聯的類一塊兒運行。
  2. 提升了類的複用。
  3. 增長了類的透明度。
  4. 靈活性好。
缺點
  1. 過多地使用適配器,會讓系統很是零亂,不易總體進行把握。好比,明明看到調用的是 A 接口,其實內部被適配成了 B 接口的實現,一個系統若是太多出現這種狀況,無異於一場災難。所以若是不是頗有必要,能夠不使用適配器,而是直接對系統進行重構。
  2. 因爲 JAVA 至多繼承一個類,因此至多隻能適配一個適配者類,並且目標類必須是抽象類。
注意點
  1. 目標接口能夠忽略,此時目標接口和源接口其實是相同的
  2. 適配器類能夠是抽象類
  3. 能夠有帶參數的適配器模式

3.1.4 一個充電器案例

// 充電器只能接受USB接口
public class Charger {
    public static void main(String[] args) throws Exception{
        USB usb = new SuperAdapter(new TypeC());
        connect(usb);
        usb = new SuperAdapter(new Lightning());
        connect(usb);
    }

    public static void connect(USB usb) {
        usb.power();
        usb.data();
    }
}

// 充電器的接口都是USB的,假設有兩個方法分別是電源和數據
interface USB {
    void power();
    void data();
}

// IOS的Lightning接口
class Lightning {
    void iosPower() {
        System.out.println("IOS Power");
    }
    void iosData() {
        System.out.println("IOS Data");
    }
}

// TYPE-C接口
class TypeC {
    void typeCPower() {
        System.out.println("TypeC Power");
    }
    void typeCData() {
        System.out.println("TypeC Data");
    }
}

// 超級適配器,能夠適配多種手機機型
class SuperAdapter implements USB {
    private Object obj;

    public SuperAdapter(Object obj) {
        this.obj = obj;
    }

    @Override
    public void power() {
        if(obj.getClass() == Lightning.class) {
            ((Lightning)obj).iosPower();
        } else if(obj.getClass() == TypeC.class) {
            ((TypeC)obj).typeCPower();
        }
    }

    @Override
    public void data() {
        if(obj.getClass() == Lightning.class) {
            ((Lightning)obj).iosData();
        } else if(obj.getClass() == TypeC.class) {
            ((TypeC)obj).typeCData();
        }
    }
}

3.2 缺省適配模式

缺省適配模式爲一個接口提供缺省實現,這樣子類型能夠從這個缺省實現進行擴展,而沒必要從原有接口進行擴展。ios

3.2.1 缺省適配模式結構

缺省適配模式

簡單的例子
  • 下面程序中,Monk接口定義了兩個方法,因而它的子類必須實現這兩個方法。
  • 但出現了一個LuZhiShen,他只能實現一部分方法,另外一部分方法沒法實現
  • 因此須要一個抽象的適配類MonkAdapter實現此Monk接口,此抽象類給接口全部方法都提供一個空的方法,LuZhiShen只須要繼承該適配類便可。
// 和尚
interface Monk {
    void practiceKungfu();
    void chantPrayer();
}

abstract class MonkAdapter implements Monk {
    @Override
    public void practiceKungfu() {}

    @Override
    public void chantPrayer() {}
}

class LuZhiShen extends MonkAdapter {
    @Override
    public void practiceKungfu() {
        System.out.println("拳打鎮關西");
    }
}

3.2.2 細節

使用場景
  • 任什麼時候候不許備實現一個接口中全部方法的時候
做用

缺省適配器模式可使所須要的類沒必要實現不須要的接口。數據庫

核心點
  • 缺省適配的類必須是抽象類,由於這個類不該當被實例化
  • 缺省適配的類提供的方法必須是具體的方法,而不是抽象的方法。

3.3 組合模式

組合模式,就是在一個對象中包含其餘對象,這些被包含的對象多是終點對象(再也不包含別的對象),也有多是非終點對象(其內部還包含其餘對象)。
咱們將對象稱爲節點,即一個根節點包含許多子節點,這些子節點有的再也不包含子節點,而有的仍然包含子節點,以此類推。很明顯,這是樹形結構,終結點叫葉子節點,非終節點叫樹枝節點,第一個節點叫根節點。編程

3.3.1 安全式的合成模式結構

安全式的合成模式要求管理集合的方法只出如今樹枝結點(Composite)中,而不出如今樹葉結點中。
安全式的合成模式設計模式

  • 抽象構建(Component)角色:這是一個抽象角色,他給參加組合的對象定義出公共的接口及其默認行爲,能夠用來管理全部的子對象。
  • 樹葉(Leaf)角色:樹葉是沒有子對象的對象,定義出參加組合的原始對象的行爲。
  • 樹枝(Composite)角色:表明參加組合的有下級子對象的對象。樹枝構件類給出全部管理子對象的方法。

3.3.2 透明的合成模式結構

透明的合成模式
透明的合成模式要求全部的具體構建類,都符合一個固定的接口。安全

3.4 裝飾器模式

裝飾器模式(Decorator)容許向一個現有的對象添加新的功能,同時又不改變其結構。這種類型的設計模式屬於結構型模式,它是做爲現有的類的一個包裝。ide

3.4.1 裝飾器結構

裝飾器模式

  • 抽象構件(Component)角色:給出一個抽象結構,以規範準備接受附加責任的對象。
  • 具體構件(Concrete Component)角色:定義一個要接受附加責任的類
  • 裝飾(Decorator)角色:持有一個構件對象的實例,並定義一個與抽象構件接口一致的接口。
  • 具體裝飾(Concrete Decorator)角色:負責給構件對象「貼上」附加的責任。

3.4.2 裝飾器細節

使用場景
  1. 須要擴展一個類的功能
  2. 須要動態地給一個對象增長功能,這些功能能夠再動態的插銷
  3. 須要增長由一些基本功能的排列組合而產生很是大量的功能
優勢
  1. 更加靈活:裝飾模式和繼承關係的目的都是要擴展對象的功能,可是裝飾模式比繼承更加靈活
  2. 多樣性:經過使用不一樣具體裝飾類及其排列組合,能夠創造出不一樣的行爲
  3. 動態擴展:裝飾器能夠動態擴展構件類
缺點
  1. 會產生比繼承關係更多的對象
  2. 比繼承更加容易出錯
注意點
  1. 裝飾類的接口必須與被裝飾類的接口相容。
  2. 儘可能保持抽象構件(Component)簡單。
  3. 能夠沒有抽象的(Component),此時裝飾器(Decorator)通常是具體構件(Concrete Component)的一個子類。
  4. 裝飾(Decorator)和具體裝飾(Concrete Decorator)能夠合併。
InputStream及其子類

InputStream

  • 抽象構件(Component)角色:InputStream
  • 具體構件(Concrete Component)角色:ByteArrayInputStreamPipedInputStreamStringBufferInputStream等原始流處理器。
  • 裝飾(Decorator)角色:FilterInputStream
  • 具體裝飾(Concrete Decorator)角色:DateInputStreamBufferedInputStreamLineNumberInputStream
OutputStream及其子類

也用到類裝飾器模式this

3.4.3 例子

// Component:一個藝人
interface Artist {
    void show();
}

// Concrete Component:一個歌手
class Singer implements Artist {
    @Override
    public void show() {
        System.out.println("Let It Go");
    }
}

// 裝飾後的歌手:不只會唱歌,還會講笑話和跳舞
class SuperSinger implements Artist {
    private Artist role;
    
    public SuperSinger(Artist role) {
        this.role = role;
    }
    @Override
    public void show() {
        System.out.println("Tell Jokes!");
        role.show();
        System.out.println("Dance!");
    }
}

3.5 代理模式

代理模式給某一個對象提供一個代理對象,並由代理對象控制對原對象對引用spa

3.5.1 代理模式結構

代理模式

  • 抽象主題(Subject)角色:聲明瞭真實主題和代理主題的共同接口
  • 代理主題(Proxy)角色:代理主題角色內部含有對真實主題的引用,從而能夠在任什麼時候候操做真實主題對象;代理主題角色提供一個與真實主題角色相同的接口,以即可以在任什麼時候候均可以替代真實主題控制對真實主題的引用,負責在須要的時候建立真實主題對象(和刪除真實主題對象);代理角色一般在將客戶端調用傳遞給真實的主題以前或以後,都要執行某個操做,而不是單純地將調用傳遞給真實主題對象。
  • 真實主題(RealSubject)角色:定義了代理角色所表明的真實對象。
public class Test {
    public static void main(String[] args) {
        Subject subject = new RealSubject();
        Subject proxy = new ProxySubject(subject);
        proxy.request(); // 此處經過代理類來執行
    }
}

interface Subject {
    void request();
}

class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject");
    }
}

class ProxySubject implements Subject {
    private Subject subject;

    public ProxySubject(Subject subject) {
        this.subject = subject;
    }

    @Override
    public void request() {
        System.out.println("ProxySubject");
    }
}

3.5.2 動態代理

自從JDK 1.3之後,Java在java.lang.reflect庫中提供了一下三個類直接支持代理模式:ProxyInvocationHanderMethod設計

動態代理步驟
  1. 建立一個真實對象
  2. 建立一個與真實對象有關的調用處理器對象InvocationHandler
  3. 建立代理,把調用處理器和要代理的類聯繫起來Proxy.newInstance()
  4. 在調用處理對象的invoke()方法中執行相應操做
public class Test {
    public static void main(String[] args) {
        // 建立要被代理的實例對象
        Subject subject = new RealSubject();
        // 建立一個與被代理實例對象有關的InvocationHandler
        InvocationHandler handler = new ProxySubject(subject);
        // 建立一個代理對象來代理subject,被代理的對象subject的每一個方法執行都會調用代理對象proxySubject的invoke方法
        Subject proxySubject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class[]{Subject.class}, handler);
        // 代理對象執行
        proxySubject.request();
    }
}

interface Subject {
    void request();
}

class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject");
    }
}

class ProxySubject implements InvocationHandler {
    private Subject subject;

    public ProxySubject(Subject subject) {
        this.subject = subject;
    }

    /**
     * @param proxy 要代理的
     * @param method
     * @param args
     * @return
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before Proxy");
        Object obj = method.invoke(subject, args);
        System.out.println("After Proxy");
        return obj;
    }
}
  • 可使用範型來建立ProxySubject
  • 可使用匿名內部類減小代碼數量請查看14.7節

3.5.3 細節

優勢
  1. 代理類和真實類分離,職責清晰。
  2. 在不改變真是累代碼的基礎上擴展了功能。
缺點
  1. 因爲在客戶端和真實主題之間增長了代理對象,所以有些類型的代理模式可能會形成請求的處理速度變慢。
  2. 實現代理模式須要額外的工做,有些代理模式的實現很是複雜。
和適配器模式的關係

適配器模式的用意是改變所考慮對象的接口,而代理模式不能改變。

和裝飾模式
  • 裝飾模式應當爲所裝飾的對象提供加強功能
  • 代理模式對對象的使用施加控制,並不提供對象自己的加強功能
虛擬代理
  • 虛擬代理模式(Virtual PRoxy)會推遲真正所需對象實例化時間。在須要真正的對象工做以前,若是代理對象可以處理,那麼暫時不須要真正對象來出手。
  • 當一個真實主題對象的加載須要耗費資源時,一個虛擬代理對象能夠代替真實對象接受請求,並展現「正在加載」的信息,並在適當的時候加載真實主題對象。

3.6 享元模式

享元模式以共享的方式高效地支持大量的細粒度對象。

3.6.1 單純享元模式

單純享元模式中,全部的享元對象都是能夠共享的。
單純享元模式

  • 抽象享元(Flyweight)角色:是全部具體享元角色的超類,併爲這些類規定公共接口。
  • 具體享元(Concrete Flyweight)角色:實現抽象享元的接口。若是由內蘊狀態的話,必須負責爲內蘊狀態提供空間。
  • 享元工廠(Flyweight Factory)角色:負責建立和管理享元角色。若是系統中有了則返回該角色,沒有則建立。
  • 客戶端(Client)角色:維護一個全部享元對象的引用。存儲全部享元對象的外蘊狀態。

3.6.2 複合享元模式

複合享元模式

  • 抽象享元(Flyweight)角色 :給出一個抽象接口,以規定出全部具體享元角色須要實現的方法。
  • 具體享元(ConcreteFlyweight)角色:實現抽象享元角色所規定出的接口。若是有內蘊狀態的話,必須負責爲內蘊狀態提供存儲空間。
  • 複合享元(ConcreteCompositeFlyweight)角色 :複合享元角色所表明的對象是不能夠共享的,可是一個複合享元對象能夠分解成爲多個自己是單純享元對象的組合。複合享元角色又稱做不可共享的享元對象(UnsharedConcreteFlyweight)。
  • 享元工廠(FlyweightFactory)角色 :負責建立和管理享元角色。當一個客戶端對象調用一個享元對象的時候,若是已經有了,享元工廠角色就應當提供這個已有的享元對象;若是系統中沒有一個適當的享元對象的話,享元工廠角色就應當建立一個合適的享元對象。

3.6.3 細節

內蘊狀態和外蘊狀態

內蘊狀態:是存儲在享元對象內部的,不會隨環境改變而改變的。一個享元能夠具備內蘊狀態並能夠共享。
外蘊狀態:隨環境改變而改變、不能夠共享的狀態。享元對象的外蘊狀態必須由客戶端保存,並在享元對象被建立以後,在須要使用的時候再傳入到享元對象內部。

不變模式

享元模式中的對象不必定非要是不變對象,但大多數享元對象的確是這麼設計的。

享元工廠
  1. 使用單例模式:通常只須要一個享元工廠,能夠設計成單例的。
  2. 備忘錄模式:享元工廠負責維護一個表,經過這個表把不少相同的實例與它們的一個對象聯繫起來。
優勢

減小對象的建立,下降內存消耗

缺點
  1. 提升了系統的複雜度,爲了使對象能夠共享,須要將一些狀態外部化
  2. 須要將一些狀態外部化,而讀取外部狀態是的運行時間稍微變長
使用場景
  1. 一個系統中有大量對象。
  2. 這些對象消耗大量內存。
  3. 這些對象的狀態大部分能夠外部化。
  4. 這些對象能夠按照內蘊狀態分爲不少組,當把外蘊對象從對象中剔除出來時,每一組對象均可以用一個對象來代替。
  5. 系統不依賴於這些對象身份,換言之,這些對象是不可分辨的。
Java應用
  1. String對象,有則返回,沒有則建立一個字符串並保存
  2. 數據庫的鏈接池

3.6.4 案例

依舊是熟悉的KFC點餐爲例:

  • 外蘊狀態:點餐的顧客
  • 內蘊狀態:顧客點的食物
  • 具體享元角色:維護內蘊狀態(客人要點的食物)。
public class KFC {
    public static void main(String[] args) {
        OrderFactory orderFactory = OrderFactory.getInstance();
        Order order = orderFactory.getOrder(Food.MiniBurger);
        order.operation("李雷");
        order = orderFactory.getOrder(Food.MiniBurger);
        order.operation("韓梅梅");
    }
}

enum Food {
    MiniBurger,
    MexicanTwister,
    CornSalad,
    HotWing,
    PepsiCola
}

// Flyweight角色
interface Order {
    // 傳入的是外蘊對象:顧客
    void operation(String customer);
}

// ConcreteFlyweight角色
class FoodOrder implements Order {
    // 內蘊狀態
    private Food food;
    // 構造方法,傳入享元對象的內部狀態的數據
    public FoodOrder(Food food) {
        this.food = food;
    }

    @Override
    public void operation(String customer) {
        System.out.println("顧客[" + customer + "]點的是" + food.toString());
    }
}

// FlyweightFactory角色
class OrderFactory {
    private Map<Food, Order> orderPool = new HashMap<>();
    private static OrderFactory instance = new OrderFactory();

    private OrderFactory() {}

    public static OrderFactory getInstance() {
        return instance;
    }

    // 獲取Food對應的享元對象
    public Order getOrder(Food food) {
        Order order = orderPool.get(food);
        if (null == order) {
            order = new FoodOrder(food);
            orderPool.put(food, order);
        }
        return order;
    }
}

3.7 門面模式

門面模式(Facade Pattern)要求一個子系統的外部與其內部通訊,必須經過一個統一的門面對象進行。

3.7.1 門面模式結構

門面模式沒有一個通常化的類圖描述,能夠用下面的例子來講明。
門面模式

  • 門面(Facade)角色:外部能夠調用這個角色的方法。此角色知道子系統的功能和責任。
  • 子系統(Subsystem)角色:能夠有多個子系統,子系統不須要知道門面的存在。

3.7.2 細節

門面數量

一般只須要一個門面類,並且只有一個實例,所以能夠設計稱單例模式。固然也可有多個類。

使用場景
  1. 爲一個複雜的子系統提供一個簡單的接口
  2. 使子系統和外部分離開來
  3. 構建一個層次化系統時,可使使用Facade模式定義系統中每一層,實現分層。
優勢
  1. 減小系統之間的相互依賴。
  2. 提升了安全性。
缺點
  1. 不符合開閉原則
  2. 若是要改東西很麻煩,繼承重寫都不合適。
Java例子

MVC三層結構

3.7.3 KFC例子

假如沒有服務員(門面),顧客(外部系統)要點一個套餐須要知道每一個套餐包含的食物(子系統)種類,這樣就會很是麻煩,因此最好的方式是直接告訴服務員套餐名稱就行了。

public class Customer {
    public static void main(String[] args) {
        Waiter waiter = new Waiter();
        List<Food> foodList = waiter.orderCombo("Combo1");
    }
}

abstract class Food {}
class MiniBurger extends Food {}
class MexicanTwister extends Food {}
class CornSalad extends Food {}
class HotWing extends Food {}
class PepsiCola extends Food {}

class Waiter {
    public List<Food> orderCombo(String comboName) {
        List<Food> foodList;
        switch (comboName) {
            case "Combo1" : 
                foodList = Arrays.asList(new MiniBurger(), new CornSalad(), new PepsiCola()); 
                break;
            case "Combo2":
                foodList = Arrays.asList(new MexicanTwister(), new HotWing(), new PepsiCola());
                break;
            default:
                foodList = new ArrayList<>();
        }
        return foodList;
    }
}

3.8 過濾器模式

過濾器模式使用不一樣的條件過濾一組對象,並經過邏輯操做以解耦方式將其連接。這種類型的設計模式屬於結構模式,由於該模式組合多個標準以得到單個標準。

3.8.1 細節

步驟
  1. 建立一個要過濾的普通類,要有得到其私有屬性的get方法
  2. 建立一個接口,規定過濾方法
  3. 實現接口,能夠依須要重寫過濾方法,參數傳遞的通常是存儲過濾類的容器類
  4. 複雜過濾類能夠經過設置傳遞接口參數(複用其餘基礎過濾類)來實現多重過濾
Java8

Java8中的lambda表達式能夠更簡單的實現過濾器

List<Movie> movies = Stream.of(
                new Movie("大話西遊","comedy"),
                new Movie("泰囧", "comedy"),
                new Movie("禁閉島", "suspense"))
                .filter(var -> "comedy".equals(var.getType()))
                .collect(Collectors.toList());

3.8.2 電影的例子

  1. 建立被過濾的類Movie,根據它的type屬性實現過濾
  2. 建立接口Criteria,規定過濾方法
  3. 建立喜劇電影過濾器ComedyMovieCriteria,根據comedy==movie.type來過濾出須要的喜劇電影
public class Test {
    public static void main(String[] args) {
        List<Movie> movies = new ArrayList(){{
            add(new Movie("大話西遊","comedy"));
            add(new Movie("泰囧", "comedy"));
            add(new Movie("禁閉島", "suspense"));
        }};
        System.out.println(new ComedyMovieCriteria().meetCriteria(movies));
    }
}

// 被篩選的對象
class Movie {
    private String name;
    // 電影類型
    private String type;

    public Movie(String name, String type) {
        this.name = name;
        this.type = type;
    }
    // getters & setters & toString
}

// 過濾器接口
interface Criteria {
    /**
     * @param movies 要被篩選的電影
     * @return 篩選後的結果
     */
    List<Movie> meetCriteria(List<Movie> movies);
}

// 過濾喜劇電影的過濾器,要求是movie.type==comedy
class ComedyMovieCriteria implements Criteria {
    @Override
    public List<Movie> meetCriteria(List<Movie> movies) {
        List<Movie> result = new ArrayList<>();
        for (Movie movie : movies) {
            if ("comedy".equals(movie.getType())) {
                result.add(movie);
            }
        }
        return result;
    }
}

3.9 橋接模式

橋接模式是將抽象化實現化解耦,使二者能夠獨立地變化。橋接模式有助於理解面向對象的設計原則,包括開閉原則以及組合聚合複用原則

3.9.1 橋接模式結構

橋接模式

這個系統含有兩個等級結構
  • 由抽象化角色和修正抽象化角色組成的抽象化等級結構。
  • 由實現化角色和兩個具體實現化角色所組成的實現化等級結構。
橋接模式所涉及的角色
  • 抽象化(Abstraction)角色:抽象化給出的定義,並保存一個對實現化對象的引用。
  • 修正抽象化(Refined Abstraction)角色:擴展抽象化角色,改變和修正父類對抽象化的定義。
  • 實現化(Implementor)角色:這個角色給出實現化角色的接口,但不給出具體的實現。必須指出的是,這個接口不必定和抽象化角色的接口定義相同,實際上,這兩個接口能夠很是不同。實現化角色應該只給出底層操做,而抽象化角色應該只給出基於底層操做的更高一層的操做。
  • 具體實現化(Concrete Implementor)角色:這個角色給出實現化角色接口的具體實現。

3.9.2 細節

抽象化、實現化、解耦

抽象化:存在於多個實體中的共同的概念性聯繫;經過忽略一些信息,把不一樣的實體看成相同的實體來對待。
實現化:抽象化給出的具體實現就是實現化。一個類的實例就是這個類的實現化,一個子類就是它超類的實現化。
解耦:耦合就是兩個實體的某種強關聯,把它們的強關聯去掉就是解耦。
強關聯與弱關聯:所謂強關聯,就是在編譯期已經肯定的,沒法在運行期動態改變的關聯;所謂弱關聯,就是能夠動態地肯定而且能夠在運行期動態地改變的關聯。繼承是強關聯,而聚合關係是弱關聯

核心理解

橋接模式中的脫耦,就是在抽象化和實現化之間使用組合關係而不是繼承關係,從而使二者能夠相對獨立的變化。

優勢
  1. 實現抽象化和實現化的分離。
  2. 提升了代碼的擴展能力。
  3. 實現細節對客戶透明。
缺點
  1. 橋接模式的引入會增長系統的理解與設計難度
  2. 因爲聚合關聯關係創建在抽象層,要求開發者針對抽象進行設計與編程。
使用場景
  1. 若是一個系統須要在構件的抽象化角色和具體化角色之間增長更多的靈活性,避免在兩個層次之間創建靜態的繼承聯繫,經過橋接模式可使它們在抽象層創建一個關聯關係。
  2. 抽象化角色和實現化角色能夠以繼承的方式獨立擴展而互不影響,在程序運行時能夠動態將一個抽象化子類的對象和一個實現化子類的對象進行組合,即系統須要對抽象化角色和實現化角色進行動態耦合。
  3. 一個類存在兩個獨立變化的維度,且這兩個維度都須要進行擴展。
  4. 雖然在系統中使用繼承是沒有問題的,可是因爲抽象化角色和具體化角色須要獨立變化,設計要求須要獨立管理這二者。
  5. 對於那些不但願使用繼承或由於多層次繼承致使系統類的個數急劇增長的系統,橋接模式尤其適用
Java例子

大多數的驅動器(Driver)都是橋接模式的應用,使用驅動程序的應用系統就是抽象化角色,而驅動器自己扮演實現化角色。
JDBC驅動器

3.9.3 發送消息的案例

  • 下面案例中,SendMsg及其子類是按照發送消息的方式進行擴展的;而Send是按照發送消息的時間進行擴展的,二者互不影響。
  • Send持有類一個SendMsg對象,並可使用此對象的方法。
// Implementor角色
interface SendMsg {
    void sendMsg();
}

// Concrete Implementor角色
class EmailSendMsg implements SendMsg {
    @Override
    public void sendMsg() {
        System.out.println("Send Msg By Email");
    }
}

// Concrete Implementor角色
class WeChatSendMsg implements SendMsg {
    @Override
    public void sendMsg() {
        System.out.println("Send Msg By WeChat");
    }
}

// Abstraction 角色
abstract class Send {
    protected SendMsg sendMsg;

    public Send(SendMsg sendMsg) {
        this.sendMsg = sendMsg;
    }

    public abstract void send();
}

// Concrete Implementor角色
class ImmediatelySend extends Send {
    public ImmediatelySend(SendMsg sendMsg) {
        super(sendMsg);
    }

    @Override
    public void send() {
        sendMsg.sendMsg();
        System.out.println("Send Msg Immediately");
    }
}

// Concrete Implementor角色
class DelayedlySend extends Send {
    public DelayedlySend(SendMsg sendMsg) {
        super(sendMsg);
    }

    @Override
    public void send() {
        sendMsg.sendMsg();
        System.out.println("Send Msg DelayedlySend");
    }
}
相關文章
相關標籤/搜索