使用C# (.NET Core) 實現適配器模式 (Adapter Pattern) 和外觀模式 (Facade Pattern)

来源:https://www.cnblogs.com/cgzl/archive/2018/04/16/8854372.html
-Advertisement-
Play Games

本文的概念內容來自深入淺出設計模式一書 現實世界中的適配器(模式) 我帶著一個國標插頭的筆記本電腦, 來到歐洲, 想插入到歐洲標準的牆壁插座裡面, 就需要用中間這個電源適配器. 面向對象的適配器 你有個老系統, 現在來了個新供應商的類, 但是它們的介面不同, 如何使用這個新供應商的類呢? 首先, 我 ...


本文的概念內容來自深入淺出設計模式一書

現實世界中的適配器(模式)

我帶著一個國標插頭的筆記本電腦, 來到歐洲, 想插入到歐洲標準的牆壁插座裡面, 就需要用中間這個電源適配器.

面向對象的適配器

你有個老系統, 現在來了個新供應商的類, 但是它們的介面不同, 如何使用這個新供應商的類呢?

首先, 我們不想修改現有代碼, 你也不能修改供應商的代碼. 那麼你只能寫一個可以適配新供應商介面的類了:

這裡, 中間的適配器實現了你的類所期待的介面, 並且可以和供應商的介面交互以便處理你的請求.

適配器可以看作是中間人, 它從客戶接收請求, 並把它們轉化為供應商可以理解的請求:

所有的新代碼都寫在適配器裡面了.

鴨子的例子

有這麼一句話不知道您聽過沒有: 如果它路像個鴨子, 叫起來也像個鴨子, 那它就是個鴨子. (例如: Python裡面的duck typing)

這句話要是用來形容適配器模式就得這麼改一下: 如果它走路像個鴨子, 叫起來也像個鴨子, 那麼它可能是一個使用了鴨子適配器的火雞....

看一下代碼的實現:

鴨子介面:

namespace AdapterPattern.Abstractions
{
    public interface IDuck
    {
        void Quack();
        void Fly();
    }
}

野鴨子:

using AdapterPattern.Abstractions;

namespace AdapterPattern
{
    public class MallardDuck : IDuck
    {
        public void Fly()
        {
            System.Console.WriteLine("Flying");
        }

        public void Quack()
        {
            System.Console.WriteLine("Quack");
        }
    }
}

火雞介面:

namespace AdapterPattern.Abstractions
{
    public interface ITurkey
    {
        void Gobble();
        void Fly();
    }
}

野火雞:

using AdapterPattern.Abstractions;

namespace AdapterPattern.Turkies
{
    public class WildTurkey : ITurkey
    {
        public void Fly()
        {
            System.Console.WriteLine("Gobble gobble");
        }

        public void Gobble()
        {
            System.Console.WriteLine("I'm flying a short distance");
        }
    }
}

火雞適配器:

using AdapterPattern.Abstractions;

namespace AdapterPattern.Adapters
{
    public class TurkeyAdapter : IDuck
    {
        private readonly ITurkey turkey;

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

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

        public void Quack()
        {
            turkey.Gobble();
        }
    }
}

測試運行:

using System;
using AdapterPattern.Abstractions;
using AdapterPattern.Adapters;
using AdapterPattern.Turkies;

namespace AdapterPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            DuckTestDrive();
        }

        static void DuckTestDrive()
        {
            IDuck duck = new MallardDuck();
            var turkey = new WildTurkey();
            IDuck turkeyAdapter = new TurkeyAdapter(turkey);

            System.Console.WriteLine("Turkey says.........");
            turkey.Gobble();
            turkey.Fly();

            System.Console.WriteLine("Duck says.........");
            TestDuck(duck);

            System.Console.WriteLine("TurkeyAdapter says.........");
            TestDuck(turkeyAdapter);
        }

        static void TestDuck(IDuck duck)
        {
            duck.Quack();
            duck.Fly();
        }
    }
}

這個例子很簡單, 就不解釋了.

理解適配器模式

Client 客戶實現了某種目標介面, 它發送請求到適配器, 適配器也實現了該介面, 並且適配器保留著被適配者的實例, 適配器把請求轉化為可以在被適配者身上執行的一個或者多個動作.

客戶並不知道有適配器做著翻譯的工作.

其他:

適配器可以適配兩個或者多個被適配者.

適配器也可以是雙向的, 只需要實現雙方相關的介面即可.

適配器模式定義

適配器模式把一個類的介面轉化成客戶所期待的另一個介面. 適配器讓原本因介面不相容而無法一起工作的類成功的工作在了一起.

類圖:

其中 Client只知道目標介面, 適配器實現了這個目標介面, 適配器是通過組合的方式與被適配者結合到了一起, 所有的請求都被委托給了被適配者.

對象適配器和類適配器

一共有兩類適配器: 對象適配器類適配器.

之前的例子都是對象適配器.

為什麼沒有提到類適配器? 

因為類適配器需要多繼承, 這一點在Java和C#裡面都是不可以的. 但是其他語言也許可以例如C++?

它的類圖是這樣的:

這個圖看著也很眼熟, 這兩種適配器唯一的區別就是: 類適配器同時繼承於目標和被適配者, 而對象適配器使用的是組合的方式來把請求傳遞給被適配者.

通過鴨子的例子來認識兩種適配器的角色

類適配器:

類適配器裡面, 客戶認為它在和鴨子談話, 目標就是鴨子類, 客戶調用鴨子上面的方法. 火雞沒有和鴨子一樣的方法, 但是適配器可以接收鴨子的方法調用並把該動作轉化為調用火雞上面的方法. 適配器讓火雞可以響應一個針對於鴨子的請求, 實現方法就是同時繼承於鴨子類和火雞類

對象適配器:

對象適配器里, 客戶仍然認為它在和鴨子說話, 目標還是鴨子類, 客戶調用鴨子類的方法, 適配器實現了鴨子類的介面, 但是當它接收到方法調用的時候, 它把該動作轉化委托給了火雞. 火雞並沒有實現和鴨子一樣的介面, 多虧了適配器, 火雞(被適配者)將會接收到客戶針對鴨子介面的方法調用.

兩種適配器比較:

對象適配器: 使用組合的方式, 不僅能是配一個被適配者的類, 還可以適配它的任何一個子類.

類適配器: 只能適配一個特定的類, 但是它不需要重新實現整個被適配者的功能. 而且它還可以重寫被適配者的行為.

對象適配器: 我使用的是組合而不是繼承, 我通過多寫幾行代碼把事情委托給了被適配者. 這樣很靈活.

類適配器: 你需要一個適配器和一個被適配者, 而我只需要一個類就行.

對象適配器: 我對適配器添加的任何行為對被適配者和它的子類都起作用.

...

又一個適配器的例子 (Java)

 老版本的java有個介面叫做Enumeration:

後來又出現了一個Iterator介面:

現在我想把Enumeration適配給Iterator:

這個應該很簡單, 可以這樣設計:

只有一個問題, Enumeration不支持remove動作, 也就是說適配器也無法讓remove變成可能, 所以只能這樣做: 拋出一個不支持該操作的異常(C#: NotSupportedException), 這也就是適配器也無法做到完美的地方.

看一下這個java適配器的實現:

裝飾模式 vs 適配器模式

你可能發現了, 這兩個模式有一些相似, 那麼看看它們之間的對話:

裝飾模式: 我的工作全都是關於職責, 使用我的時候, 肯定會涉及到在設計里添加新的職責或行為.

適配器模式: 我主要是用來轉化介面.

裝飾模式: 當我裝飾一個大號介面的時候, 真需要寫很多代碼.

適配器模式: 想把多個類整合然後提供給客戶所需的介面, 這也是很麻煩的工作. 但是熟話說: "解耦的客戶都是幸福的客戶..."

裝飾模式: 用我的時候, 我也不知道已經套上多少了裝飾器了.

適配器模式: 適配器幹活的時候, 客戶也不知道我們的存在. 但是我們允許客戶在不修改現有代碼的情況下使用新的庫, 靠我們來轉化就行.

裝飾模式: 我們只允許為類添加新的行為, 而無需修改現有代碼.

適配器模式: 所以說, 我們總是轉化我們所包裹的介面.

裝飾模式: 我們則是擴展我們包裝的對象, 為其添加行為或職責.

從這段對話可以看出, 裝飾模式和適配器模式的根本區別就是它們的意圖不同.

 

另一種情況

現在我們可以知道, 適配器模式會把類的介面轉化成客戶所需要的樣子.

但是還有另外一種情況也需要轉化介面, 但卻處於不同的目的: 簡化介面. 這就需要使用外觀模式(Facade Pattern). 

外觀模式會隱藏一個或多個類的複雜性, 並提供一個整潔乾凈的外觀(供外界使用).

現在在總結一下這三種模式的特點:

裝飾者模式: 不修改介面, 但是添加職責.

適配器模式: 把一個介面轉化成另外一個.

外觀模式: 把介面變得簡單.

 

一個需求 -- 家庭影院

這個家庭影院有DVD播放器, 投影儀, 屏幕, 環繞立體音響, 還有個爆米花機:

你可能花了幾周的時間去連線, 組裝.....現在你想看一個電影, 步驟如下:

  1. 打開爆米花機
  2. 開始製作爆米花
  3. 把燈光調暗
  4. 把屏幕放下來
  5. 把投影儀打開
  6. 把投影儀的輸入媒介設為DVD
  7. 把投影儀調整為寬屏模式
  8. 打開功放
  9. 把功放的輸入媒介設為DVD
  10. 把功放設置為環繞立體聲
  11. 把功放的音量調到中檔
  12. 把DVD播放器打開
  13. 開始播放DVD

具體用程式描述就是這樣的:

  • 目前一共是這些步驟
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 由於工作需要,接觸了大半年時間的Django+xadmin框架,一直沒空對這塊對進行相關的梳理。最近在同事的慫恿下,就在這分享下筆者的學習及工作經驗吧。 好了,話不多說,下麵開始進入正題: 環境需求: 筆者的工作系統環境:Mac 10.13.4+Python3.6.x+Django2.0.x+Xad ...
  • Java中Set集合是如何實現添加元素保證不重覆的? Set集合是一個無序的不可以重覆的集合。今天來看一下為什麼不可以重覆。 ...
  • import turtlet=turtle.Pen()for i in range(100): t.forward(i) t.left(45) ...
  • 面向對象的組合用法 軟體重用的重要方式除了繼承之外還有另外一種方式,即:組合 組合指的是,在一個類中以另外一個類的對象作為數據屬性,稱為類的組合。 ...
  • 概述 UWP Community Toolkit 中有一個 Animations 的集合,它們可以幫助開發者實現很多的動畫,本篇我們先來看一下 Animations 的功能都有哪些,再後面會針對每一種 Animations 做詳細的代碼分析。 Animations 集合涵蓋了很多種類的動畫,我們先來 ...
  • 這篇博客的標題用了一個疑問句,源於我們公司的代碼評審,深刻的討論了單例模式的使用場景及其與靜態方法來說有何不同,這次討論確實讓我真正的理解了單例模式的使用,雖然說理解還一定全面,但必須作為一個認知的提升。告訴了我自己,對於編程,不懂的太多,原理性的東西還需要持續的學習。 進入正文,我們來討論一下,什 ...
  • 主題 之前簡單介紹了Asp.net core 的初步的使用,本篇我打算給大家介紹一下Identity的架構,讓大家對Identity有一個總體的理解和認識。 簡介 博客原文歡迎訪問我的博客網站,地址是:[深入理解Aspnet Core之Identity(4) ] : https://www.blue ...
  • 使用Json.Net可以把一個Json字元串轉換成一個JObject對象,如果有已知強類型,如果有已知對應的強類型,可以直接轉成對應的類型。但如果沒有,要訪問Json裡面對應的數據的時候,就顯得比較麻煩。我們可以藉助DynamicObject來訪問對應的屬性。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...