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

来源:https://www.cnblogs.com/skyseavae/archive/2019/03/06/10486438.html
-Advertisement-
Play Games

前言 這一章主要講2個模式,一個是,適配器模式(負責將一個類的介面適配成用戶所期待的),另外一個是外觀模式(為子系統提供一個共同的對外介面),看完的第一反應是,為什麼要把它們兩放在同一章,難道它們有什麼不可告人的秘密? 難道是因為他們倆都很簡單嗎?不會不會,畢竟是大名鼎鼎的headfirst,怎麼可 ...


前言

這一章主要講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,在新的子系統加入,或者子系統介面變更時,可能需要修改外觀類或客戶端的源代碼,違背了“開閉原則”。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、1、color: 文本顏色 預定義文本顏色值,如red,blue等 十六進位的顏色值 #fff白色 建議常用的表示方法 RGB代碼,如紅色可以表示為rgb(255,0,0)或rgb(100%,%0,%0) 2、行間距: line-height line-height: 10px; 3、text- ...
  • element的table中使用 <template slot-scope="scope"> </template> 包裹想要插入的input,或者select等HTML元素,<el-table>綁定一個的數組對象,在input或者select等HTML元素使用 v-model="scope.row ...
  • 隨著科技的進步,及人們日常生活節奏的加快,我們通常花費在手機等移動設備上的時間比使用電腦的時間越來越多,為了適應市場及用戶的轉變,越來越多的服務從PC端轉向移動端,就導致移動端有著強大的發展前景和巨大的市場,作為一個Web高級前端開發工程師,移動端頁面佈局也成了我們必須掌握的技能之一。 ...
  • 在做網頁時前端時,使用IE打開時會出現標題欄DIV被遮擋PDF遮擋, 後在stackoverflow中查到是IE瀏覽器的問題:鏈接https://stackoverflow.com/questions/12911428/z-index-does-not-work-in-internet-explor ...
  • 3D建築,bim技術,3d庫房,3d檔案室,3d密集架,webGL,threejs,3d機房 ...
  • 直接進入正題,滑鼠跟隨,顧名思義,就是元素會跟隨著滑鼠的移動而作出相應的運動。大概類似於這樣: 通常而言,CSS 負責表現,JavaScript 負責行為。而滑鼠跟隨這種效果屬於行為,要實現通常都需要藉助 JS。 當然,本文的重點,就是介紹如何在不藉助 JS 的情況下使用 CSS 來模擬實現一些滑鼠 ...
  • Web前端工程師是當前各大企業都比較稀缺的人才,薪資待遇和就業前景都很不錯。不論是專業還是非專業,有基礎亦或是無基礎,都想通過學習Web前端實現高薪就業。 ...
  • 一、新的變數聲明方式 let/cons 與var不同,新的變數聲明方式帶來了一些不一樣的特性,其中最重要的兩個特性就是提供了塊級作用域與不再具備變數提升。 若是對變數提升不怎麼瞭解的話可以去參考我的其他文章 javascript預編譯的過程 。 什麼是塊級作用域膩? 寫在 “{}” 內的內容 都是塊 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...