headfirst設計模式(8)—適配器模式與外觀模式


前言

這一章主要講2個模式,一個是,適配器模式(負責將一個類的接口適配成用戶所期待的),另外一個是外觀模式(為子系統提供一個共同的對外接口),看完的第一反應是,為什么要把它們兩放在同一章,難道它們有什么不可告人的秘密?

難道是因為他們倆都很簡單嗎?不會不會,畢竟是大名鼎鼎的headfirst,怎么可能這么草率,這我是萬萬不相信的!

細想了一下,我和工作的點點滴滴,我發現,一般到項目的后期,好像都比較容易用上這兩個東西...

當然,項目的后期並不是說一個項目自己從頭發開到尾的項目,而是在它生命周期的后半段,比如適配器,用來適配老的接口,比如外觀模式,用來隱藏各個子系統,各個模塊的協作細節。

 

不過外觀模式卻不一定都是在后期才發力的:

1,前期如果系統比較復雜,在系統規划的時候,就會有意識的對系統分層,為上層模塊提供一些高級的api。

2,在系統的中期呢,開發過程中,發現子系統越來越復雜,也可以提供類似的操作。

3,在系統后期,模塊越來越多,功能越來越復雜,還有歷史原因,外觀模式就更加有用了,畢竟,有一個簡單易用的API,比手動調用各個系統的邏輯那簡直是不要太舒服!

 

為什么后期不重構?而是要做這些修修補補的工作呢?

舉個例子,房子上有棵樹,你覺得這棵樹很礙事,就把樹給干掉了,因為你以為,是房子上面長的,結果呢?特么是樹把房子吊着的!類似的坑實在是太多了,所以,重構需謹慎,且構且珍惜。

當然不是說重構不好,而是要綜合考量各方面的因素,而且,重構也用得上這些啊,畢竟,重構不是重寫...(誒,重寫好像也要用)

 

適配器模式

先說說它是干嘛的,用通俗一點的話來講就是,VGA轉DVI,2線插頭轉3線插頭...廢話不多說,上個圖就知道了

什么?大家很想看個例子?那么我就來一個例子吧,就舉一個小火雞變成小鴨子的故事吧

先看看鴨子接口(對應Target)

/**
 * 鴨子接口
 */
public interface Duck {
    /**
     * 鴨叫
     */
    void quack();

    /**
     * 飛行
     */
    void fly();
}

然后看一下火雞的接口和實現類(對應Adaptee)

/**
 * 火雞接口
 */
public interface Turkey {
    /**
     * 火雞叫
     */
    void gobble();

    /**
     * 飛行
     */
    void fly();
}

/**
 * 野火雞
 */
public class WildTurkey implements Turkey {
    public void gobble() {
        System.out.println("咯咯");
    }
 
    public void fly() {
        System.out.println("我在飛,雖然我飛的很近");
    }
}

首先可以看出,它們的之間有一些共同之處,都有叫聲,都可以飛行,這個也是適配的前提,有共同點!

如果沒有共同點,是不是去隔壁的其他設計模式看看?

OK,接下來開始適配操作

火雞適配器(Adapter)

/**
 * 火雞適配器
 */
public class TurkeyAdapter implements Duck {
    Turkey turkey;//持有一個火雞對象
 
    public TurkeyAdapter(Turkey turkey) {
        this.turkey = turkey;
    }

    /**
     * 鴨叫
     */
    public void quack() {
        turkey.gobble();
    }

    /**
     * 飛行
     */
    public void fly() {
        //適配的時候,這里模擬飛行5次
        for(int i= 0; i < 5; i++) {
            turkey.fly();
        }
    }
}

適配器的邏輯也很簡單

首先,實現Duck接口,要讓Client能夠調用,那么首先得長得和別人一樣啊

其次,持有一個真正的處理對象,然后再根據Duck接口來進行適配,比如這里,quack接口,就直接調用Turkey#gobble(),而fly()可能是因為某種神秘力量,需要火雞飛行的距離和鴨子一樣遠,所以需要手動去適配,在這里添加了適配的代碼

最后,適配器的作用就是把一個類轉換成另外一個類,轉換的時候可能需要一些邏輯上的處理,讓它能符合用戶的期待

測試下是不是成功的偽裝了呢

public class DuckClient {
    public static void main(String[] args) {

        //初始化一只火雞
        WildTurkey turkey = new WildTurkey();
        //偽裝成一只鴨子
        Duck duck = new TurkeyAdapter(turkey);

        System.out.println("鳴叫:");
        duck.quack();

        System.out.println("------------------");

        System.out.println("飛行:");
        duck.fly();
    }
}

結果:

適配器模式模式確實很簡單,但是確實也很實用,優點很明顯,可以將目標類和適配者解耦,不需要改動原來的結構(新增了Adapter來封裝了適配的邏輯),但是建議不要在系統設計階段就盲目的使用它,增加系統的復雜度

外觀模式

這個就更簡單了,例子我可以舉一堆,比如說,酒店前台的小姐姐,餐廳前台的小姐姐,醫院的小姐姐...

核心思想:為子系統們提供一套通用的對外接口(高級API)

為什么會有這樣的需求呢?

各個子系統在設計過程中,或者在實際使用的過程中會發現,有一些通用的步驟,對於更加高的調用層來說,它們其實不需要知道底層是通過哪些步驟來實現的,更多的是,以一個統一的接口來調用。

比如,在想在家里搞一個家庭影院,需要以下步驟:

1,燈光不能太亮,亮度需要調低到10

2,需要打開投影機,並且要調整到寬屏模式

3,音響需要調整成環繞立體音,音量設置成5

4,打開DVD開始播放

代碼如下:

燈光:

/**
 * 影院燈光
 */
public class TheaterLights {
    String description;

    public TheaterLights(String description) {
        this.description = description;
    }

    public void on() {
        System.out.println(description + " 打開");
    }

    public void off() {
        System.out.println(description + " 關閉");
    }

    public void dim(int level) {
        System.out.println(description + " 亮度調節到:" + level  + "%");
    }

    public String toString() {
        return description;
    }
}
View Code

投影儀:

/**
 * 投影儀屏幕
 */
public class Screen {
    String description;

    public Screen(String description) {
        this.description = description;
    }

    public void up() {
        System.out.println(description + " 上升");
    }

    public void down() {
        System.out.println(description + " 下降");
    }


    public String toString() {
        return description;
    }
}
/**
 * 投影儀
 */
public class Projector {
    String description;
    DvdPlayer dvdPlayer;
    
    public Projector(String description, DvdPlayer dvdPlayer) {
        this.description = description;
        this.dvdPlayer = dvdPlayer;
    }
 
    public void on() {
        System.out.println(description + " 打開");
    }
 
    public void off() {
        System.out.println(description + " 關閉");
    }

    public void wideScreenMode() {
        System.out.println(description + " 調整到寬屏模式");
    }

    public void tvMode() {
        System.out.println(description + " 調整到tv模式");
    }
  
    public String toString() {
            return description;
    }
}
View Code

音響:

/**
 * 音響
 */
public class Amplifier {
    String description;

    public Amplifier(String description) {
        this.description = description;
    }
 
    public void on() {
        System.out.println(description + " 打開");
    }
 
    public void off() {
        System.out.println(description + " 關閉");
    }

    //立體聲
    public void setStereoSound() {
        System.out.println(description + " 立體聲模式");
    }

    //環繞聲
    public void setSurroundSound() {
        System.out.println(description + " 環繞聲模式");
    }
 
    public void setVolume(int level) {
        System.out.println(description + " 調整音量到: " + level);
    }

    public String toString() {
        return description;
    }
}
View Code

DVD播放器:

/**
 * DVD播放器
 */
public class DvdPlayer {
    String description;
    int currentTrack;
    Amplifier amplifier;
    String movie;
    
    public DvdPlayer(String description, Amplifier amplifier) {
        this.description = description;
        this.amplifier = amplifier;
    }
 
    public void on() {
        System.out.println(description + " 播放");
    }
 
    public void off() {
        System.out.println(description + " 關閉");
    }

    public void play(String movie) {
        this.movie = movie;
        currentTrack = 0;
        System.out.println(description + " 播放 \"" + movie + "\"");
    }

    public String toString() {
        return description;
    }
}
View Code

不重要的代碼就折疊了,免得難得看,不使用外觀模式,需要調用一堆代碼:

/**
 * 不使用外觀模式
 */
public class Client {
    public static void main(String[] args) {
        Amplifier amp = new Amplifier("Top-O-Line 揚聲器");
        DvdPlayer dvd = new DvdPlayer("Top-O-Line DVD播放器", amp);
        Projector projector = new Projector("Top-O-Line 投影儀", dvd);
        TheaterLights lights = new TheaterLights("客廳燈");
        Screen screen = new Screen("投影儀銀幕");

        System.out.println("准備看電影...");
        lights.dim(10);
        screen.down();
        projector.on();
        projector.wideScreenMode();
        amp.on();
        amp.setSurroundSound();
        amp.setVolume(5);
        dvd.on();
        dvd.play("奪寶奇兵");
    }
}

使用外觀模式,一行解決:

/**
 * 使用外觀模式后的測試類
 */
public class FacadeClient {

    private static HomeTheaterFacade HOME_THEATER;
    static{
        Amplifier amp = new Amplifier("Top-O-Line 揚聲器");
        DvdPlayer dvd = new DvdPlayer("Top-O-Line DVD播放器", amp);
        Projector projector = new Projector("Top-O-Line 投影儀", dvd);
        TheaterLights lights = new TheaterLights("客廳燈");
        Screen screen = new Screen("投影儀銀幕");

        HOME_THEATER = new HomeTheaterFacade(amp, dvd, projector, screen, lights);
    }

    public static void main(String[] args) {
        //看電影
        HOME_THEATER.watchMovie("奪寶奇兵");
    }
}

我擦?咋還是這么多行?

static塊里面的代碼是初始化代碼,一般使用spring,都是依賴注入的東西,其實調用就一行:

HOME_THEATER.watchMovie("奪寶奇兵");

一鍵解決就是爽啊,如果說對比的話,相當於,去網上買了個床,小哥送來的是一堆零件讓你組裝,和小哥送來就是一張組裝好了的床啊!

但是能夠一鍵解決的,更多的是一些通用的操作,比如說,例子中,燈光不能太亮,你想把它調到5,不想用默認的10,,那么可能就只能自己寫一遍外觀模式封裝的邏輯了。

那么這里就有個問題了,能不能重載方法,讓它支持可以自定義燈光亮度這個參數呢?對於這個我只能說,要看業務需求了,如果100個人里面只有1個人用,那么對於系統產生的復雜度可能比 產生的價值高,反過來,可能就需要去實現。

但是,如果這種需求越來越多,系統變得越來越復雜,那外觀模式還是一個簡單可愛的小姐姐嗎?如果不實現,就無法達到隱藏子系統復雜度的痛點,如果實現,就會產生新的API調用的復雜度,我終於知道為啥我特么還在學習設計模式了...

說了這么多,說說它的優缺點吧

優點:

1,對客戶屏蔽了子系統組件使用起來門檻更低。

2,實現了子系統與客戶之間的松耦合關系。

3,雖然提供了訪問子系統的統一入口,但是並不影響用戶直接使用子系統類。

缺點:

1,通過外觀類訪問子系統時,減少了可變性和靈活性。

2,在新的子系統加入,或者子系統接口變更時,可能需要修改外觀類或客戶端的源代碼,違背了“開閉原則”。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM