【面向對象設計原則】之依賴倒置原則(DIP)

来源:http://www.cnblogs.com/vaiyanzi/archive/2017/05/25/6904449.html
-Advertisement-
Play Games

依賴倒轉原則(Dependency Inversion Principle, DIP):抽象不應該依賴於細節,細節應當依賴於抽象。換言之,要針對抽象(介面)編程,而不是針對實現細節編程。 開閉原則(OCP)是面向對象設計原則的基礎也是整個設計的一個終極目標,而依賴倒置原則(DIP )則是實現OCP原 ...


依賴倒轉原則(Dependency Inversion  Principle, DIP):抽象不應該依賴於細節,細節應當依賴於抽象。換言之,要針對抽象(介面)編程,而不是針對實現細節編程。

開閉原則(OCP)是面向對象設計原則的基礎也是整個設計的一個終極目標,而依賴倒置原則(DIP )則是實現OCP原則的一個基礎,換句話說開閉原則(OCP)是你蓋一棟大樓的設計藍圖,那麼依賴倒置原則就是蓋這棟大樓的一個鋼構框架,沒有鋼構架構是很難順利蓋起一棟大樓的,同樣的在面向對象軟體設計的過程中不遵守依賴倒置原則是很難開發出符合開閉原則的軟體的。更不用說開發出易於維護,易於升級的軟體。 因此開閉原則是非常重要的一個原則,它有很強的實操性,並且能夠直接指導我們寫代碼代碼。

通常要符合這個原則的第一步就是針對抽象編程,類之間的依賴關係儘量去使用高層抽象不要使用底層的實現細節,從軟體工程來說高層抽象是較穩定的,也就是說抽象具有一定的穩定性,而實現細節較不穩定,也就是說實現細節具有易變性,而我們期望軟體具有更好的穩定性,顯而易見我們在開發的時候自然而然的要走穩定路線(依賴抽象編程)。這個原則也是對軟體工程中要求“高聚低偶”實踐一個保障和指導。

我們來看一個例子假設我們在開發一個軟體產品需要一個日誌系統,要將系統產生的一些重要事情記錄在記事本上。通常我們的實現如下:

    public class Logger
    {
        public void Info(string infoText)
        {
            Console.WriteLine($"[{DateTime.Now}][Info]:{infoText}");
        }

        public void Debug(string debugText)
        {
            Console.WriteLine($"[{DateTime.Now}][Debug]:{debugText}");
        }

        public void Warn(string warmText)
        {
            Console.WriteLine($"[{DateTime.Now}][Warm]:{warmText}");
        }

        public void Error(string errorText,Exception exception)
        {
            Console.WriteLine($"[{DateTime.Now}][Error]:{errorText} - Exception:{exception.Message}");
        }
    }

客戶端調用如下:

    static void Main(string[] args)
    {
        Logger logger = new Logger();
        logger.Info("This is a info text.");
        logger.Debug("This is a debug text.");
        logger.Warn("This is a warn text.");
        logger.Error("This is a error text", new Exception("This is a exception."));

        Console.ReadKey();
    }

輸出:

image

這看起來還不錯,一切都是那麼自然。但是隨著時間的推移,產品做的好買了很多客戶,產品變得越來越大,使用Logger 類的地方成千上萬處,可怕的事情終於發生了:

A 客戶提出來我想把日誌存在資料庫中便於做統計分析。

B 客戶說我想把日誌列印在一個控制臺上便於我時時監測系統運行情況。

C 客戶說我要把日誌存到Windows Azure  Storage上。

。。。。

客戶越來越多奇葩需求不斷涌出。我們的產品變得很難修改,很難維護,很難去適合所有的客戶。 怎麼辦呢? 回過頭來看看我們的這個日誌系統的設計才恍然大悟:沒有遵守面向對象設計原則的依賴倒置原則和開閉原則了。知道就好,找到法門了, 我們將日誌這一塊的設計重構一下讓其符合OCP和DIP應該就可以了。 那麼我們就要首先抽象寫日誌的介面ILog, 讓實際調用的地方調用高層抽象(ILog),具體的實現類TextLogger,ConsoleLogger,DatabaseLogger,AzureStorageLogger都繼承自ILog介面,然後我們在利用反射加配置,不同的用戶配置不同的具體實現類,這樣問題就迎任而解了。 看代碼:

    public interface ILog
    {
        void Info(string infoText);
        void Debug(string debugText);
        void Warn(string warmText);
        void Error(string errorText, Exception exception);
    }
    public class TextLogger:ILog
    {
        public void Info(string infoText)
        {
            Console.WriteLine($"[{DateTime.Now}][Info]:{infoText}");
        }

        public void Debug(string debugText)
        {
            Console.WriteLine($"[{DateTime.Now}][Debug]:{debugText}");
        }

        public void Warn(string warmText)
        {
            Console.WriteLine($"[{DateTime.Now}][Warm]:{warmText}");
        }

        public void Error(string errorText,Exception exception)
        {
            Console.WriteLine($"[{DateTime.Now}][Error]:{errorText} - Exception:{exception.Message}");
        }
    }

    public class DatabaseLogger:ILog
    {
        public void Info(string infoText)
        {
            Console.WriteLine($"[{DateTime.Now}][Info]:{infoText}");
        }

        public void Debug(string debugText)
        {
            Console.WriteLine($"[{DateTime.Now}][Debug]:{debugText}");
        }

        public void Warn(string warmText)
        {
            Console.WriteLine($"[{DateTime.Now}][Warm]:{warmText}");
        }

        public void Error(string errorText,Exception exception)
        {
            Console.WriteLine($"[{DateTime.Now}][Error]:{errorText} - Exception:{exception.Message}");
        }
    }
    public class ConsoleLogger:ILog
    {
        public void Info(string infoText)
        {
            Console.WriteLine($"[{DateTime.Now}][Info]:{infoText}");
        }

        public void Debug(string debugText)
        {
            Console.WriteLine($"[{DateTime.Now}][Debug]:{debugText}");
        }

        public void Warn(string warmText)
        {
            Console.WriteLine($"[{DateTime.Now}][Warm]:{warmText}");
        }

        public void Error(string errorText,Exception exception)
        {
            Console.WriteLine($"[{DateTime.Now}][Error]:{errorText} - Exception:{exception.Message}");
        }
    }

    public class AzureStorageLogger:ILog
    {
        public void Info(string infoText)
        {
            Console.WriteLine($"[{DateTime.Now}][Info]:{infoText}");
        }

        public void Debug(string debugText)
        {
            Console.WriteLine($"[{DateTime.Now}][Debug]:{debugText}");
        }

        public void Warn(string warmText)
        {
            Console.WriteLine($"[{DateTime.Now}][Warm]:{warmText}");
        }

        public void Error(string errorText,Exception exception)
        {
            Console.WriteLine($"[{DateTime.Now}][Error]:{errorText} - Exception:{exception.Message}");
        }
    }

添加一個配置在Config中:

    <appSettings>
        <add key="Logger" value="ConsoleApp1.TextLogger"/>
    </appSettings>

客戶端的調用改成調用ILog:

    static void Main(string[] args)
    {
        string key = ConfigurationManager.AppSettings["Logger"];
        ILog logger = ObjectBuildFactory<ILog>.Instance(key);
        logger.Info("This is a info text.");
        logger.Debug("This is a debug text.");
        logger.Warn("This is a warn text.");
        logger.Error("This is a error text", new Exception("This is a exception."));

        Console.ReadKey();
    }

輸出:

image

A客戶期望將日誌寫在資料庫中只需要將配置改成下麵這樣就可以了:

    <appSettings>
        <add key="Logger" value="ConsoleApp1.DatabaseLogger"/>
    </appSettings>

根據不同的客戶需求只需要改這個配置的value值就可以了。

要使上面的代碼順利運行我們要加一個輔助類用於反射:

public class ObjectBuildFactory<T>
{
    public static T Instance(string key)
    {
        Type obj = Type.GetType(key);
        if (obj == null) return default(T);

        T factory = (T)obj.Assembly.CreateInstance(obj.FullName);

        return factory;
    }
}

那麼有一天E客戶說他們公司有自己的日誌系統並開發了一套日誌分析工具,他們可以開放API讓我們把日誌直接存到他們的日誌系統中去。 這次好辦了啊,只需要定義一個具體類繼承自ILog介面並實現所有的方法,在每一個實現的方法中調用客戶的API, 最後將實現的類配置到配置文件中就可以很好的滿足客戶的要求了, 這樣是不是很完美呢?我們完全遵守了DIP和OCP原則,也很好的使用了LSP,使得我們軟體變得穩定,應對需求的變化變得簡單了,也易於升級和易於維護了。

在使用DIP是需要註意一下幾點

1. 繼承自高層介面的類要實現所有介面中的方法。

2.子類中除了介面的方法,在用介面聲明的對象調用的地方是無法被調用到的。除非直接調用子類,但是直接調用子類是違背DIP的。

3. DIP是實現OCP的重要原則保障,一般違背了DIP很難不違背OCP,可以看這一篇【面向對象設計原則】之開閉原則(OCP)

4.LSP 是實現DIP的基礎,多態給實現DIP提供了可能。 可以看這一篇 【面向對象設計原則】之里氏替換原則(LSP)


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

-Advertisement-
Play Games
更多相關文章
  • “註解”這個詞,可謂是在Java編程中出鏡率比較高,而且也是一個老生常談的話題。我們之前在聊Spring相關的東西時,註解是無處不在,之前我們簡單的聊過一些“註解”的相關內容,比如在Spring中是如何進行“註解”組合的。因為註解在Java編程中還是比較重要的,所以我們今天的博客就把註解的東西給系統 ...
  • 問題:Firemonkey Android 平臺顯示斜粗體文字時,文字右方會有顯示不全的問題。 修正代碼: 請將 FMX.FontGlyphs.Android.pas 複製到自己的工程目錄下,再修改如下代碼: 修正效果: ...
  • 數據源連接池配置 ...
  • 列印流 在整個 包中,列印流是輸出信息最方便的類,主要包含 位元組列印流 ( )和 字元列印流 ( )。列印流提供了非常方便的列印功能,可以列印任何的數據類型,例如:小數、整數、字元串等等,相對於前面學習的幾個文件的操作來說,這裡的列印流是最簡便的一個類了 PrintStream 主要功能是格式化的將 ...
  • 記憶體操作流 之前的所有的流操作都是針對文件的,但是有時候只是想要實現數據間轉換,此時如果我們想要創建一個文件然後再刪除文件,那樣顯得有點麻煩,因此此時的記憶體操作流就顯得很適合這類的操作,因為它只是在記憶體中存儲,並不會真正的創建文件,記憶體操作流涉及的兩個類是 ,`ByteArrayOutputStre ...
  • 在上一回合談到,客戶端應用程式的所有操作都在主線程上進行,所以一些比較耗時的操作可以在非同步線程上去進行,充分利用CPU的性能來達到程式的最佳性能。對於Unity而言,又提供了另外一種『非同步』的概念,就是協程( ),通過反編譯,它本質上還是在主線程上的優化手段,並不屬於真正的多線程( )。那麼問題來了 ...
  • 一個單子(Monad)說白了不過就是自函子範疇上的一個么半群而已,這有什麼難以理解的? " " 之前瞭解了下Monad,後來一段時間沒碰,最近研究Parser用到Monad時發現又不懂了。現在重新折騰,趁著記憶還熱乎,趕緊寫下來。本文不會完整講解Monad,而只介紹Monad相關的思想與編程技巧。 ...
  • threading模塊 threading.Thread(target = func,argc = (arg1,arg2,)[,name=""]) 參數必須是元祖類型 threading.start()/threading.join([timeout=None]) 程式開始/掛起,直到結束 [+]s ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...