把介面當作引數傳入

不愿透露姓名的小村村發表於2024-10-17

1. 基礎概念

  • 前段時間一直在看設計模式的基礎概念,總結起來其實也就是一些老生常談的各種原則和定義,初看這些原則和定義實來枯燥乏味,但是一番實踐之後,卻又發現它們簡練而不失準確性,故貼在筆記開頭,方便隨時對照:
 - 目標:高內聚,低耦合/複用性高(一些相互關聯的方法就應該放一個類裡面,叫高內聚;類與類之間需要更少的依賴,叫做低耦合)
 - 設計原則:
    - 單一職責原則:一個類負責的東西需要專精
    - 依賴倒置原則:需要面向介面(抽象類)程式設計,而不是面向實現程式設計
    - 開閉原則:擴充原有程式碼的方式應該是在原有程式碼的基礎上擴充,而不是直接修改原有的程式碼
    - 介面隔離原則:創造更多的專用介面,而不是用一個單一的總介面
    - 里氏替換原則:父類出現的地方都可以使用子類替代,且功能不會發生影響
    - 合成複用原則:盡力使用物件的組合/聚合(集合關係和組成關係),而不要過多的使用繼承
    - 迪米特原則:互動的內容儘量小,影響才會最小(透過方法傳參,透過欄位,透過屬性等等方式用來互動會比較好,比較清晰)

    開閉原則是目標,里氏轉換原則是基礎,依賴倒置原則是手段。
    隔離變換。
    針對介面程式設計,而不是針對實現程式設計。

2. 關於介面的探討

  • 經過一段時間的OOP以及設計模式的訓練之後,我逐漸習慣用介面隔離變化的思維方式:即把物件分類,用介面來區分不同類的區別(它們可能需要實現的東西是一樣的,但是具體實現的方法是不同的,比如對模組寫入電壓模式或者電阻模式,其實都是寫入模式<方法名>,但是寫入的具體資料內容和長度那是完全不同的<方法體>)
  • 和繼承相比,雖然父類也可以用virtual這種虛方法,再在子類中ovrride來實現。但是父類畢竟不如介面靈活(一個介面就封裝一類變化,但是一個父類用來封裝一組變化始終感覺比較奇怪);另外介面不需要繼承,只需要實現,所以可以對需要實現介面的類細分出好多種不同的介面,程式的靈活性又+1.
  • 還是原則上提的:針對介面程式設計,而不是針對實現程式設計。,父類老老實實當一個abstract的容器最好,OOP上多用介面,少用繼承。(一個父類可以被好多個子類繼承,那麼在實現的時候,就可以透過開閉原則來使得程式更加靈活,畢竟可以一對多實現(當然,介面也有這樣的特點,甚至比繼承更靈活))。
  • 說到一對多實現這一點,這裡就多說一點關於簡單工廠,反射工廠,抽象工廠這幾種最常用的設計模式:
    • 簡單工廠:它也不屬於設計模式,它的簡單原理就是透過switch..case of來判定當前輸入的Enum元素是哪個,判斷出哪個就返回哪個,靈活性+1;
    • 反射工廠,它在基本原來上和簡單工廠一樣,但是加入一些語法糖(比如C#的default(T) = typeof()這種反射機制),省略switch的硬編碼,使得程式更加簡單易懂。
    • 抽象工廠,這就是利用抽象父類當個容器,你想要實現實現哪個子類你就在建構函式中以哪個子類來建立物件,反正抽象父類型別可以對應所有的例項子類。

3. 好多地方喜歡把介面當作引數傳入的原因?

  • 最開始我的理解如上,介面就是用來隔離變化,以更好的override變化。所以每次如果需要實現某種介面的時候,我就直接實現介面對應的類就好了。
  • 但是:如果想更加靈活的實現介面(比如我寫了很多個類,每一個類都繼承了同一個介面,但這個時候這些類是沒有一個統一的父類的,做不到一對多(里氏轉換原則)),這個時候就需要把介面當作一個共同的型別,你只要知道呼叫時介面是透過哪個類實現的,你就可以實現靈活的呼叫介面
  • 看程式碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace test
{
    //定義介面
    public interface ILogger
    {
        void Log(string message);
    }
    //實現介面的類
    public class ConsoleLogger : ILogger
    {
        public void Log(string message)
        {
            Console.WriteLine(message);
        }
    }
    //使用介面作為引數傳遞的方法
    public class Processer
    {
        private readonly ILogger _logger;
        public Processer(ILogger logger)//從建構函式處實現介面的傳入
        {
            _logger = logger;
        }
        public void Process()//使用介面的方法,但在真實呼叫之前這些方法都是虛的
        {
            _logger.Log("abcd");
            _logger.Log("efgh");
        }
    }
    //呼叫過程,此時才真正給介面分配真實實現方法
    public class Program
    {
        public static void Main(string[] args)
        {
            ILogger logger = new ConsoleLogger();//此處真正關聯介面和介面的實現
            Processer processer = new Processer(logger);//傳入已經例項後的介面
            processer.Process();//呼叫實現方法
        }
    }
}

相關文章