結構型模式之介面卡模式、橋接模式與裝飾器模式(一)

MXC肖某某發表於2021-10-28

一、基本介紹

  結構型模式(Structural Pattern)關注如何將現有類或物件組織在一起形成更加強大的結構。分為兩種:1,類結構型模式:關心類的組合,由多個類可以組合成一個更大的系統,在類結構型模式中一般只存在繼承關係和實現關係;2,物件結構型模式:關心類與物件的組合,通過關聯關係使得在一個類中定義另一個類的例項物件,然後通過該物件呼叫其方法,更符合“合成複用原則”。

  具體的結構型模式可分為:

  • 介面卡模式(Adapter Pattern)
  • 橋接模式(Bridge)
  • 裝飾者模式(Decorator)
  • 組合模式(Composite Pattern)
  • 外觀模式(Facade)
  • 享元模式(Flyweight Pattern)
  • 代理模式(Proxy)

二、介面卡模式

1,基本介紹

  介面卡模式(Adapter Pattern)將某個類的介面轉換成客戶端期望的另一個介面表示,主要的目的是相容性,讓原本介面不匹配不能一起工作的兩個類可以協同工作。其別名為包裝器(Wrapper)。

  介面卡模式屬於結構型模式,主要分為三類:類介面卡模式、物件介面卡模式、介面介面卡模式

2,工作原理

  • 介面卡模式:將一個類的介面轉換成另一個種介面,讓原本介面不相容的類可以相容
  •  從使用者的角度看不到被適配者,是解耦的
  • 使用者呼叫介面卡轉化出來的目標介面方法,介面卡再呼叫被適配者的相關介面方法
  • 使用者接收到反饋結果,感覺只是和目標介面互動

3,類介面卡模式

a)類介面卡模式介紹

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

b)類介面卡應用例項

 需求

  以生活中充電器為例,充電器本身相當於Adapter,220V交流電相當於src(即被適配者),我們的目標dst(即目標)是5V直流電

 思路分析(類圖)

  結構型模式之介面卡模式、橋接模式與裝飾器模式(一)  

 程式碼實現

public class Voltage220V {
    public int output220v() {
        int src = 220;
        System.out.println("原始電壓=" + src + "伏");
        return src;
    }
}

public interface IVoltage5V {
    public int output5v();
}

public class VoltageAdapter extends Voltage220V implements IVoltage5V {
    @Override
    public int output5v() {
        int v220 = output220v();
        return v220 / 44;
    }
}

public class Phone {
    public void chrging(IVoltage5V iVoltage5V) {
        if (iVoltage5V.output5v() == 5) {
            System.out.println("電壓5V,可以充電");
        } else {
            System.out.println("電壓" + iVoltage5V.output5v() + "V,無法充電");
        }
    }
}

public class Client {
    public static void main(String[] args) {
        System.out.println("==========類介面卡模式=========");
        Phone phone = new Phone();
        phone.chrging(new VoltageAdapter());
    }
}

c)注意事項與細節

  • 缺點:Java是單繼承機制,所以類介面卡需要繼承src,故而dst必須是介面,有一定侷限性
  • src類的方法在Adapter中都會暴露出來,也增加了使用的成本
  • 優點:由於其繼承了src類,所以它可以根據需求重寫src類的方法,使得Adapter的靈活性增強了

4,物件介面卡模式

a)物件介面卡模式介紹

  基本思路和類的介面卡模式相同,只是將Adapter類做修改,不再繼承src類,而是持有src類的例項(聚合),以解決相容性的問題。即:持有src類,實現dst類介面,完成src -> dst的適配。

  即根據“合成複用原則”,在系統中儘量使用關聯關係(聚合)來替代繼承關係。物件介面卡模式是介面卡模式中常用的一種

b)物件介面卡應用例項

 需求

  同類介面卡

 思路分析(類圖)

  結構型模式之介面卡模式、橋接模式與裝飾器模式(一)

 程式碼實現

  除了VoltageAdapter類之外,其他的類與類介面卡相同

public class VoltageAdapter implements IVoltage5V {

    private Voltage220V voltage220V;

    public VoltageAdapter(Voltage220V voltage220V) {
        this.voltage220V = voltage220V;
    }

    @Override
    public int output5v() {
        int v220 = voltage220V.output220v();
        return v220 / 44;
    }
}

c)注意事項與細節

  • 物件介面卡和類介面卡其實算是同一種思想,只不過實現方式不同。物件介面卡根據合成複用原則,使用聚合替代繼承,所以它解決了類介面卡必須繼承src的侷限性問題,也不在要求dst必須時介面。
  • 使用成本更低,更靈活

5,介面介面卡模式

a)介面介面卡模式介紹

  • 當不需要全部實現介面提供的方法時,可先設計一個抽象類實現介面,併為該介面中每個方法提供一個預設實現(空方法),那麼該抽象類的子類可有選擇地覆蓋父類的某些方法來實現需求。
  • 適用於一個介面不想使用其他所有的方法的情況

b)介面介面卡模式例項

 類圖

  

 程式碼實現

public interface InterfaceAdapter {
    public void m1();
    public void m2();
    public void m3();
    public void m4();
}

public abstract class AbsAdapter implements InterfaceAdapter {
    @Override
    public void m1() {}

    @Override
    public void m2() {}

    @Override
    public void m3() {}

    @Override
    public void m4() {}
}

public class Client {
    public static void main(String[] args) {
        AbsAdapter absAdapter = new AbsAdapter() {
            @Override
            public void m1() {
                System.out.println("test absAdapter m1()");
            }
        };
        absAdapter.m1();
    }
}

6,介面卡模式的注意事項和細節

  • 三種命名方式,是根據src是以怎樣的形式給到Adapter(在Adapter裡的形式)來命名的。
  • 類介面卡:以類給到Adapter裡,也就是將src當做類,繼承
  • 物件介面卡:以物件給到Adapter裡,也就是將src作為一個物件,持有
  • 介面介面卡:以介面給到Adapter裡,也就是將src作為一個介面,實現
  • Adapter模式最大的作用還是將原本不相容的介面融合在一起工作

三、橋接模式

1,手機操作問題

  現在對不同手機型別的不同品牌實現操作程式設計(比如:開機、關機、上網、打電話等),如圖

  

 傳統方式解決手機操作問題

 類圖

  

 分析

  • 擴充套件性問題(類爆炸),如果我們再增加手機的樣式(旋轉式),就需要增加各個品牌手機的類,同樣如果我們增加一個手機品牌,也要在各個手機樣式類下增加。
  • 違反了單一職責原則,當我們增加手機樣式時,要同時增加所有品牌的手機,這樣增加了程式碼維護成本
  • 解決方案:橋接模式

2,橋接模式

a)基本介紹

  橋接模式(Bridge模式)是指:將實現與抽象放在兩個不同的類層次中,使兩個層次可以獨立改變。它是一種結構型設計模式。

  Bridge模式基於類的最小設計原則,通過使用封裝、聚合及繼承等行為讓不同的類承擔不同的職責。它的主要特點是把抽象(Abstraction)與行為實現(Implementation)分離開來,從而可以保持各部分的獨立性以及應對他們的功能擴充套件。

b)原理類圖

  

 原理類圖說明

  • Client類:橋接模式的呼叫者
  • 抽象類(Abstraction):維護了Implementor/即它的實現類ConcreteImplementorA/B,二者是聚合關係,Abstraction充當橋接
  • RefinedAbstraction:是Abstraction抽象類的子類
  • Implementor:行為實現類的介面
  • ConcreteImplementorA/B:行為的具體實現類
  • 從UML圖:這裡的抽象類和介面是聚合的關係,其實也是呼叫和被呼叫關係

c)解決手機操作問題

 類圖

  結構型模式之介面卡模式、橋接模式與裝飾器模式(一)

 程式碼

public interface Brand {
    void open();

    void call();

    void close();

}

public class Xiaomi implements Brand{
    @Override
    public void open() {
        System.out.println("小米手機開機");
    }

    @Override
    public void call() {
        System.out.println("小米手機打電話");
    }

    @Override
    public void close() {
        System.out.println("小米手機關機");
    }
}

public abstract class Phone {
    private Brand brand;

    public Phone(Brand brand) {
        this.brand = brand;
    }

    protected void open() {
        this.brand.open();
    }

    protected void call() {
        this.brand.call();
    }

    protected void close() {
        this.brand.close();
    }

}

public class FoldedPhone extends Phone{

    public FoldedPhone(Brand brand) {
        super(brand);
    }

    @Override
    public void open() {
        super.open();
        System.out.println("摺疊樣式手機");
    }

    @Override
    protected void call() {
        super.call();
        System.out.println("摺疊樣式手機");
    }

    @Override
    protected void close() {
        super.close();
        System.out.println("摺疊樣式手機");
    }
}

public class Client {
    public static void main(String[] args) {
        FoldedPhone foldedPhone = new FoldedPhone(new Xiaomi());
        foldedPhone.open();
    }
}

3,注意事項

  • 實現了抽象和實現部分的分離,從而極大的提高了系統的靈活性,讓抽象部分和實現部分獨立開來,這有助於系統進行分層設計,從而產生更好的結構化系統
  • 對於系統的高層部分,只需要知道抽象部分和實現部分的介面就可以了,其它部分由具體業務來完成
  • 橋接模式替代多層繼承方案,可以減少子類的個數,降低系統的管理和維護成本
  • 橋接模式的引入增加了系統的理解和設計難度,由於聚合關聯關係建立在抽象層,要求開發者對抽象進行設計和程式設計
  • 橋接模式要求正確識別出系統中兩個獨立變化的維度(抽象和實現),因此其使用範圍有一定的侷限性 

4,常用的場景

  • JDBC驅動
  • 銀行轉賬系統

    • 轉賬分類: 網上轉賬,櫃檯轉賬,AMT 轉賬
    • 轉賬使用者型別:普通使用者,銀卡使用者,金卡使用者...
  • 訊息管理
    • 訊息型別:即時訊息,延時訊息
    • 訊息分類:手機簡訊,郵件訊息,QQ 訊息...

四、裝飾者模式

1,咖啡訂單問題

  • 咖啡種類/單品咖啡:Espresso(義大利濃咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(無因咖啡)
  • 調料:Milk、Soy(豆漿)、Chocolate
  • 要求在擴充套件性的咖啡種類時,具有良好的擴充套件性、改動方便、維護方便
  • 使用OO的方式來計算不同種類咖啡的費用:單品咖啡、單品+調料組合等

2,解決方案

a)方案一

結構型模式之介面卡模式、橋接模式與裝飾器模式(一)

 類圖分析

  1)Drink 是一個抽象類,表示飲料

   -description 就是對咖啡的描述, 比如咖啡的名字

   -cost()方法就是計算費用,Drink 類中做成一個抽象方法.

  2)Decaf 就是單品咖啡,繼承 Drink, 並實現了 cost()

  3)Espress && Milk 就是單品咖啡+調料, 這個組合很多

 問題:

  這樣設計會有很多類,當我們增加一個單品咖啡,或者一個新的調料時,類的數量就會倍增,就會出現類爆炸

b)方案二

結構型模式之介面卡模式、橋接模式與裝飾器模式(一) 

 說明: milk,soy,chocolate 可以設計為 Boolean,表示是否要新增相應的調料.

 方案二問題分析

  1) 方案 2 可以控制類的數量,不至於造成很多的類

  2) 在增加或者刪除調料種類時,程式碼的維護量很大

  3) 考慮到使用者可以新增多份調料,可以將 hasMilk 返回一個對應 int

  4) 改進:考慮使用 裝飾者 模式

3,裝飾者模式

a)基本介紹

  裝飾者模式(Decorator):動態地將新功能附加到物件上。在物件功能擴充套件方面,它比繼承更有彈性,裝飾者模式也體現了開閉原則(OCP)。

b)原理

結構型模式之介面卡模式、橋接模式與裝飾器模式(一)

  •  裝飾者模式就像打包一個快遞
    • 主體:陶瓷、衣服(Component) //被裝飾者
    • 包裝:報紙填充、紙板、木板(Decorator)//裝飾者
  • Component:主體,類似前面的Drink
  • ConcreteComponent:具體的主體,比如前面的各種咖啡
  • Decorator:裝飾者,比如各種調料。裝飾者裡聚合了一個Component,也就是ConcreteComponent是可以放到裝飾者裡面的(裝飾者裡面可以包含被裝飾者)
  • ConcreteDecorator:具體的裝飾者,比如前面的各種調料

c)完成咖啡訂單問題

 uml類圖

  程式碼

//飲料父類
public abstract class Drink {
    private String description;

    private float price = 0.0f;

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        this.price = price;
    }

    //消費由子類具體實現
    public abstract float cost();
}

//咖啡
public class Coffee extends Drink{
    @Override
    public float cost() {
        return super.getPrice();
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ":" + cost();
    }
}
//美式咖啡
public class LongBlack extends Coffee{
    public LongBlack() {
        setDescription("美式...黑咖啡");
        setPrice(2.0f);
    }
}
//裝飾類,調料
public class Decorator extends Drink{
    Drink drink;

    public Decorator(Drink drink) {
        this.drink = drink;
    }

    @Override
    public float cost() {
        return drink.cost() + super.getPrice();
    }

    @Override
    public String getDescription() {
        return super.getDescription() + " " + super.getPrice() + " " + drink.getDescription();
    }
}
//具體的裝飾調料,牛奶
public class Milk extends Decorator{
    public Milk(Drink drink) {
        super(drink);
        setPrice(1.5f);
        setDescription("牛奶");
    }
}

//測試
public class Client {
    public static void main(String[] args) {
        Drink order = new LongBlack();
        System.out.println("單品:"+order.getDescription()+" 價格:"+order.cost());
        //加一份巧克力
        order = new Chocolate(order);
        System.out.println("加一份巧克力:"+order.getDescription()+" 價格:"+order.cost());
        order = new Milk(order);
        System.out.println("加一份巧克力和一份牛奶:"+order.getDescription()+" 價格:"+order.cost());
    }
}

4,JDK中的應用例項

  Java的IO結構,FilterInputStream就是一個裝飾者

結構型模式之介面卡模式、橋接模式與裝飾器模式(一)

public abstract class InputStream implements Closeable {
  ......  //Component  
}
//Decorator抽象的裝飾者 class FilterInputStream extends InputStream { //被裝飾的物件 protected volatile InputStream in; ...... }
//具體的裝飾者 public class DataInputStream extends FilterInputStream implements DataInput { ...... }
  • InputStream時抽象類,類似前面的Drink(被裝飾者)
  • FileInputStream是InputStream子類,類似前面的LongBlack、ShortBlack等單品咖啡
  • FIlterInputStream是InputStream子類,類似前面的Decorator裝飾者
  • DataInputStream是FilterInputStream子類,具體的裝飾者,類似前面的Milk、Chocolate等 

 

相關文章