結構型模式之適配器模式、橋接模式與裝飾器模式(一)

一、基本介紹

  結構型模式(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等 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章