適配器模式與外觀模式

適配器GitHub代碼
外觀GitHub代碼

適配器模式

場景描述:想象一下我們維護了一套老系統,並於一個廠商通過接口進行交互。但是最近廠商更新它們的代碼,並變更了接口格式,我們如何在不修改老系統代碼的基礎上,優雅的過渡呢?

提出方案:我們可以想象一下我們平時使用的轉換頭,這就是一種適配器。我們可以通過這種方式來優雅的“將方形放入圓形中”。

我們嘗試舉一個例子,假設現在有一個鴨子接口

public interface Duck {
    public void quack();
    public void fly();
}

並作出一個實現

public class MallardDuck implements Duck {
    @Override
    public void quack() {
        System.out.println("Quack");
    }

    @Override
    public void fly() {
        System.out.println("I'm flying");
    }
}

接着我們有另一個火雞接口

public interface Turkey {
    public void gobble();
    public void fly();
}

以及一個實現

public class WildTurkey implements Turkey {
    @Override
    public void gobble() {
        System.out.println("Gobble gobble");
    }

    @Override
    public void fly() {
        System.out.println("I'm flying a short distance");
    }
}

我們如何能夠將火雞作爲鴨子呢?可以提供一個適配器來做接口的轉換

public class TurkeyAdapter implements Duck {
    Turkey turkey;

    public TurkeyAdapter(Turkey turkey){
        this.turkey = turkey;
    }

    @Override
    public void quack() {
        turkey.gobble();
    }

    @Override
    public void fly() {
        for (int i = 0; i < 5; i++){
            turkey.fly();
        }
    }
}

然後我們測試一下

public class DuckTestDrive {
    public static void main(String[] args) {
        MallardDuck duck = new MallardDuck();
        WildTurkey turkey = new WildTurkey();

        Duck turkeyAdapter = new TurkeyAdapter(turkey);

        System.out.println("The Turkey says...");
        turkey.gobble();
        turkey.fly();

        System.out.println("\nThe Duck says...");
        testDuck(duck);

        System.out.println("\nThe TurkeyAdapter says...");
        testDuck(turkeyAdapter);
    }

    static void testDuck(Duck duck){
        duck.quack();
        duck.fly();
    }
}

可以通過結果看到我們現在將這個火雞作爲一隻鴨子的實現了。
在這裏插入圖片描述

適配器模式將一個類的接口,轉換成客戶期望的另一個接口。適配器讓原本接口不兼容的類可以合作無間。

我們看一下它的類圖
在這裏插入圖片描述
實現一個適配器所需要進行的工作,和目標接口的大小成正比。

由於Java不支持多繼承,所以適配器又分爲“對象”適配器(Java)和“類”適配器(支持多繼承的其他語言)。

再看一下“類”適配器的類圖
在這裏插入圖片描述

外觀模式

場景描述:我們現在有一個家庭影院,其中包含了DVD,投影機,自動屏幕,立體環繞聲等等,每當我們想要看電影的時候,都需要進行一系列操作;而看完電影時,又需要反向再操作一遍。

改進方案:我們嘗試進行一種近似流水線的改造,通過將一系列操作變成一個更加高層的操作。

如下是我們的改進方案的代碼

public class HomeTheaterFacade {
    Amplifier amp;
    Tuner tuner;
    DvdPlayer dvd;
    CdPlayer cd;
    Projector projector;
    TheaterLights lights;
    Screen screen;
    PopcornPopper popper;

    public HomeTheaterFacade(Amplifier amp, Tuner tuner, DvdPlayer dvd, CdPlayer cd, Projector projector, TheaterLights lights, Screen screen, PopcornPopper popper){
        this.amp = amp;
        this.tuner = tuner;
        this.dvd = dvd;
        this.cd = cd;
        this.projector = projector;
        this.lights = lights;
        this.screen = screen;
        this.popper = popper;
    }

    public void watchMovie(String movie){
        System.out.println("Get ready to watch a movie...");
        popper.on();
        popper.pop();
        lights.dim(10);
        screen.down();
        projector.on();
        projector.wideScreenMode();
        amp.on();
        amp.setDvd();
        amp.setSurroundSound();
        amp.setVolume(5);
        dvd.on();
        dvd.play(movie);
    }

    public void endMovie(){
        System.out.println("Shutting movie theater down...");
        popper.off();
        lights.on();
        screen.up();
        projector.off();
        amp.off();
        dvd.stop();
        dvd.eject();
        dvd.off();
    }

這樣我們測試一下,只可以通過一步操作就完成之前的一系列操作

public class HomeTheaterTestDrive {
    public static void main(String[] args) {
        Amplifier amp = new Amplifier();
        Tuner tuner = new Tuner();
        DvdPlayer dvd = new DvdPlayer();
        CdPlayer cd = new CdPlayer();
        Projector projector = new Projector();
        Screen screen = new Screen();
        TheaterLights lights = new TheaterLights();
        PopcornPopper popper = new PopcornPopper();

        HomeTheaterFacade homeTheater = new HomeTheaterFacade(amp, tuner, dvd, cd, projector, lights, screen, popper);

        homeTheater.watchMovie("Raiders of the Lost Ark");
        System.out.println("\n-----------Watching Movies------------\n");
        homeTheater.endMovie();
    }
}

結果如下
在這裏插入圖片描述

外觀模式提供了一個統一的接口,用來訪問子系統中的一羣接口。外觀定義了一個高層接口,讓子系統更容易使用。

外觀模式有一個很好的特徵:提供簡化的接口的同時,依然將系統完整的功能暴露出來,以供需要的人使用。
以下是它的類圖
在這裏插入圖片描述
適配器模式的意圖是,“改變”接口符合客戶的期望;而外觀模式的意圖是,提供子系統的一個簡化接口。
我們將裝飾者模式、適配器模式、外觀模式一同比較,可以看出它們的意圖分別爲:

  • 裝飾者模式,不改變接口,但加入職責
  • 適配器模式,將一個接口轉成另一個接口
  • 外觀模式,讓接口更簡單

設計原則:最少知識原則:只和你的密友談話。

當你正在設計一個系統,不管是任何對象,你都要注意它所交互的類有哪些,並注意它和這些類是如何交互的。
這個原則又名墨忒耳法則,它希望我們在設計中,不要讓太多的類耦合在一起,免得修改系統中一部分,會影響到其他部分。

這個原則提供了一些方針:就任何對象而言,在該對象的方法內,我們只應該調用屬於以下範圍的方法:

  • 該對象本身
  • 被當作方法的參數而傳遞進來的對象
  • 此方法所創建或實例化的任何對象
  • 對象的任何組件

如果調用從另一個調用中返回的對象的方法,會有什麼害處呢?如果我們這樣做,相當於向另一個對象的子部分發請求(而增加我們直接認識的對象數目)。
在這裏插入圖片描述
這個原則的缺點是,採用這個原則會導致更多的“包裝”類被製造出來,以處理和其他組件的溝通,這可能會導致複雜度和開發時間的增加,並降低運行時的性能。

總結如下:當我們需要從一個接口“轉變”爲另一個接口時,可以嘗試使用適配器;而當我們想要將子系統的一系列操作簡化時,可以使用外觀模式。

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