設計模式 (十七) 適配器、裝飾、享元模式

適配器模式

介紹

適配器模式在咱們開發中使用率極高,從最先的 ListView、GridView 到如今的 RecyclerView 都須要使用 Adapter ,而且在開發中咱們遇到的優化問題,出錯機率較大的地方也基本都出在 Adapter,這是一個讓人又愛又恨的角色。html

說到底,適配器是將兩個不兼容的類融合到一塊兒,它有點像粘合劑,將不一樣的東西經過一種轉換使得它們可以協做起來。例如,常常碰到要兩個沒有關係的類型之間進行交互,第一個解決方案是修改各自類的接口,可是若是沒有源代碼或者咱們不肯意爲了一個應用而修改各自的接口,此時怎麼辦?這種狀況咱們每每會使用一個 Adapter,在這兩種接口之間建立一個 「混血兒」 接口,這個 Adapter 會將這兩個接口進行兼容,在不修改原有代碼的狀況下知足需求。java

定義

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

使用場景

  1. 系統須要使用現有的類,而此類的接口不符合系統的須要,即接口不兼容。
  2. 想要創建一個能夠重複使用的類,用於一些彼此之間沒有太大關聯的類,包括一些可能在未來引進的類一塊兒工做。
  3. 須要統一的輸出接口,而輸入端的類型不可預知。

UML 類圖

Adapter.png

  • Target: 目標角色,也就是所期待獲得的接口。注意:因爲這裏討論的是類適配器,所以目標不能夠是類。
  • Adaptee: 如今須要適配的接口。
  • Adapter: 適配器角色,也是本模式的核心。適配器把源接口換成目標接口,固然,這一角色不能夠是接口,而必須是具體類。

代碼示例

簡單示例

適配器模分爲兩種,即類適配器和對象適配器,咱們先來看類適配器。git

業務背景: 用電源接口作例子,筆記本電腦的電源通常是 5V 電壓,可是咱們生活中的電線電壓通常都是 220V 。這個時候就出現了不匹配的情況,在軟件開發中咱們稱爲接口不兼容,此時就須要適配器來進行一個接口轉換。在軟件開發中有一句話正好體現了這點:任何問題均可以加一箇中間層來解決。這個層咱們能夠理解爲這裏的 Apapter 層,經過這層來進行一個接口轉換就達到了兼容的目的。github

在上述電源接口這個示例中, 5V 電壓就是 Target 接口,220V 電壓就是 Adapter 類,而將電壓從 220V 轉換到 5V 就是 Adapter。設計模式

類適配器:緩存

target:架構

public interface FiveVolt {

    int getVolt5();
}
複製代碼

Adapter 角色,須要被轉換的對象併發

public class Volt220 {

    public int getVolt220(){
        return 220;
    }
}
複製代碼

Adapter 角色,將 220 -> 5 V 的電壓:dom

public class VoltAdapter extends Volt220 implements FiveVolt {
    @Override
    public int getVolt5() {
        return 5;
    }
}
複製代碼

test:ide

@Test
    public void testAdapter1(){
        VoltAdapter voltAdapter = new VoltAdapter();
        System.out.println("voltAdapter:"+voltAdapter.getVolt5());
    }
複製代碼

output:

voltAdapter:5
複製代碼

Target 角色給出了須要的目標接口,而 Adapter 類則是須要被轉換的對象。Adapter 則是將 Volt220 轉換成 Target 的接口。對應的 Target 的目標是要獲取 5V 的輸出電壓,而 Adapter 真正輸出電壓是 220V ,此時就須要電源適配器類將 220V 電壓轉換爲 5V 電壓,解決接口不兼容的問題。

對象適配器:

咱們只須要變換下 Adapter 對象,以下:

public class VoltAdapter extends Volt220 implements FiveVolt {

    private Volt220 mVolt220;

    public VoltAdapter(Volt220 mVolt220) {
        this.mVolt220 = mVolt220;
    }

    @Override
    public int getVolt220() {
        return mVolt220.getVolt220();
    }

    @Override
    public int getVolt5() {
        return 5;
    }
}
複製代碼

test / output:

VoltAdapter voltAdapter = new VoltAdapter(new Volt220());
  System.out.println("voltAdapter:"+voltAdapter.getVolt5());


	voltAdapter:5
複製代碼

這種實現方式直接將要被適配的對象傳遞到 Adapter 中,使用組合的形式是吸納接口兼容的效果。這比類適配器方式更爲靈活,它的另外一個好處是被適配的對象中的方法不會暴露出來,而類適配因爲繼承了被適配對象,所以,被適配對象類的函數在 Adapter 類中也都含有,這使得 Adapter 類出現了一些奇怪的接口,用戶使用成本較高,所以,對象適配器模式更加靈活、實用。

總結

Adapter 模式的經典實如今於將本來不兼容的接口融合在一塊兒,使之可以很好的進行合做。可是,在實際開發中,Adapter 模式也有一些靈活的實現。例如 ListView 中的隔離變化,使得整個 UI 架構變得更靈活,可以擁抱變化。Adapter 模式在開發中運用很是普遍,所以,掌握 Adapter 模式是很是必要的。

優勢:

  1. 更好的複用性,系統須要使用現有的類,而此類的接口不符合系統的須要。那麼經過適配器模式就可讓這些功能獲得更好的複用。
  2. 更好的擴展性,在實現適配器功能的時候,能夠調用本身開發的功能,從而天然地擴展系統的功能。

缺點:

  1. 過多地使用適配器,會讓系統很是凌亂,不易總體把握。

裝飾模式

介紹

裝飾模式 (也稱爲 Decorator Pattern) 也稱爲包裝模式,屬於結構型模式之一,其使用一種對客戶端透明的方式來動態的擴展對象的功能,同時它也是繼承關係的一種替代方案之一。在現實生活中你也看見不少裝飾模式的例子,或者能夠大膽地說裝飾模式無處不在,就拿人來講,人須要各式各樣的衣着,無論你穿着怎麼,可是,對於我的的本質來講是不變的,充其量只是在外面披上一層 「遮羞物」 而已,這就是裝飾模式。

定義

動態的給一個對象田愛軍一些額外的職責。就是增長功能來講,裝飾模式生成子類更爲靈活。

使用場景

須要透明且動態地擴展類的功能時。

UML 類圖

65f78b7b2b7ce711c5f59e09551475b8.png

  • Component: 抽象組件,能夠試一個接口或者抽象類,其充當的就是被裝飾的原始對象。
  • ConcreteComponent: 組件具體實現類,該類是 Component 類的基本實現,也是咱們裝飾的具體對象。
  • Decorator: 抽象裝飾者。
  • ConcreteDecorator: 裝飾者具體實現類

代碼示例

業務背景: 爲 boy 穿衣

定義一個抽象的穿衣行爲:

public abstract class Person {

    /** * Person 下有一個穿着的抽象方法 */
    public abstract void dressed();
}
複製代碼

聲明一個具體行爲實現:

public class Boy extends Person {
    @Override
    public void dressed() {
        System.out.println("男孩穿着內褲");
    }
}

複製代碼

定義一個用來裝飾具體行爲的抽象:

public abstract class PersonCloth extends Person {
    protected Person person
            ;

    public PersonCloth(Person person) {
        this.person = person;
    }

    @Override
    public void dressed() {
        person.dressed();
    }
}

複製代碼

具體裝飾實現:

public class ExpensiveCloth extends PersonCloth {
    public ExpensiveCloth(Person person) {
        super(person);
    }

    @Override
    public void dressed() {
        super.dressed();
        //穿短袖
        dressShirt();
        //穿皮衣
        dressLeather();
        //穿牛仔褲
        dressJean();
    }

    private void dressShirt() {
        System.out.println("穿上短袖");
    }

    private void dressLeather() {
        System.out.println("穿上皮衣");
    }

    private void dressJean() {
        System.out.println("穿上牛仔褲");
    }
}

複製代碼
public class CheapCloth extends PersonCloth {
    public CheapCloth(Person person) {
        super(person);
    }

    @Override
    public void dressed() {
        super.dressed();
        dressShorts();
    }

    private void dressShorts() {
        System.out.println("穿條短褲");
    }
}

複製代碼

test:

@Test
    public void testDecorator(){
        //首先得有一個男孩
        Person person = new Boy();
      
        //先穿上便宜的衣服
        PersonCloth cheapCloth = new CheapCloth(person);
        cheapCloth.dressed();

        //或者在穿上有點檔次的衣服
        PersonCloth personCloth = new ExpensiveCloth(person);
        personCloth.dressed();

    }

複製代碼

output:

男孩穿着內褲
穿條短褲

男孩穿着內褲
穿上短袖
穿上皮衣
穿上牛仔褲

複製代碼

總結

裝飾模式和咱們前面講的 代理模式 有點相似,有時候甚至容易混淆,倒不是說會把代理當成裝飾,而是經常會是將裝飾看做代理,裝飾模式是以對客戶端透明的方式擴展對象的功能,是繼承關係的一個替代方案;而代理模式則是給一個對象提供一個代理對象,並有代理對象來控制對原有的對象的引用。裝飾模式應該爲所裝飾的對象加強功能;代理模式對代理的對象施加控制,但不對對象自己的功能進行加強。

享元模式

介紹

享元模式是對象池的一種實現,享元模式用來儘量減小內存使用量,它適合用於可能存在大量重複對象的場景,來緩存可共享的對象,達到對象共享、避免建立過多對象的效果,這樣一來就能夠提高性能、避免內存移除等。

定義

使用共享對象能夠有效地支持大量的細粒度的對象。

使用場景

  • 系統中存在大量的類似對象。
  • 細粒度的對象都具有較接近的外部狀態,並且內部狀態與環境無關,也就是說對象沒有特定身份。
  • 須要緩衝池的場景。

UML 類圖

  • Flyweiget : 享元對象抽象基類或者接口
  • ConcreteFlyweiget: 具體的享元對象。
  • FlyweigetFactory: 享元工廠,負責管理享元對象池和建立享元對象。

代碼示例

需求背景: 過年回家買車票,若是在併發 1W 人次同時 http 請求數據,若是後臺每次都從新建立一個查詢的車票結果,那麼必然會形成大量重複對象的建立、銷燬、使得 GC 任務繁重、內存高居不下。

展現車票信息接口

public interface Ticket {
    public void showTicketInfo(String info);
}

複製代碼

展現車票具體實現:

public class TrainTicket implements Ticket {
    public String from;
    public String to;
    public String bunk;
    public int price;

    public TrainTicket(String from, String to) {
        this.from = from;
        this.to = to;
    }

    @Override
    public void showTicketInfo(String info) {
        bunk = info;
        price = new Random().nextInt(300);
        System.out.println("購買從" + from + " -> " + to + "的 " + bunk + " 火車票 ,價格:" + price);

    }
}

複製代碼

車票信息管理:

public class TicketFactory {

    static Map<String, Ticket> sTicketMap = new ConcurrentHashMap<>();

    public static Ticket getTicket(String from, String to) {
        String key = from + "-" + to;
        if (sTicketMap.containsKey(key)) {
            //使用已經存在的對象
            System.out.println("使用存在的對象 = [" + from + "], to = [" + to + "]");
            return sTicketMap.get(key);
        } else {
            System.out.println("建立對象 = [" + from + "], to = [" + to + "]");
            TrainTicket trainTicket = new TrainTicket(from, to);
            sTicketMap.put(key, trainTicket);
            return trainTicket;
        }

    }
}


複製代碼

test:

@Test
    public void testFlaweiget(){
        Ticket ticket1 = TicketFactory.getTicket("北京", "上海");
        ticket1.showTicketInfo("上鋪");
        Ticket ticket7 = TicketFactory.getTicket("北京", "上海");
        ticket7.showTicketInfo("下鋪");
        Ticket ticket2 = TicketFactory.getTicket("北京", "上海");
        ticket2.showTicketInfo("上鋪");
        Ticket ticket3 = TicketFactory.getTicket("北京", "上海");
        ticket3.showTicketInfo("上鋪");
        Ticket ticket4 = TicketFactory.getTicket("北京", "成都");
        ticket4.showTicketInfo("下鋪");
        Ticket ticket5 = TicketFactory.getTicket("北京", "上海");
        ticket5.showTicketInfo("上鋪");
        Ticket ticket6 = TicketFactory.getTicket("北京", "上海");
        ticket6.showTicketInfo("上鋪");
    }

複製代碼

output:

建立對象 = [北京], to = [上海]
購買從北京 -> 上海的 上鋪 火車票 ,價格:36
使用存在的對象 = [北京], to = [上海]
購買從北京 -> 上海的 下鋪 火車票 ,價格:261
使用存在的對象 = [北京], to = [上海]
購買從北京 -> 上海的 上鋪 火車票 ,價格:100
使用存在的對象 = [北京], to = [上海]
購買從北京 -> 上海的 上鋪 火車票 ,價格:247
建立對象 = [北京], to = [成都]
購買從北京 -> 成都的 下鋪 火車票 ,價格:224
使用存在的對象 = [北京], to = [上海]
購買從北京 -> 上海的 上鋪 火車票 ,價格:262
使用存在的對象 = [北京], to = [上海]
購買從北京 -> 上海的 上鋪 火車票 ,價格:114

複製代碼

從上面的查詢結果得知,若是已經查詢了就使用緩存,沒有就建立對象。

總結

享元模式是實現比較簡單,可是它的做用在某些場景確實極其重要的。它能夠大大減小應用程序建立的對象,下降程序內存的佔用,加強程序的性能,但它同時也提升了系統的複雜性,須要分離出外部狀態和內部狀態,並且外部狀態具備固化特性,不該該隨內部狀態改變而改變,不然致使系統的邏輯混亂。

享元模式的有點在於大幅度地下降內存中對象的數量。可是,它作到這一點所付出的代價也是很高的。

  • 享元模式使得系統更加複雜。爲了使對象能夠共享,須要將一些狀態外部化,這使得程序的邏輯複雜化。
  • 享元模式將享元對象的狀態外部化,而讀取外部狀態使得運行時間稍微變長。

文章代碼地址

特別感謝

《 Android 源碼設計模式解析與實戰 》

感謝你的閱讀,謝謝!

相關文章
相關標籤/搜索