[鐵人賽Day08] ASP.Net Core MVC 進化之路 - Dependency Injection概念介紹

Dependency Injection簡稱DI,

可用於服務層抽換撰寫測試時的接縫

本篇將介紹DI概念及實作方式。

 

以往的專案要使用DI都需另外安裝第三方的DI Container(如Unity、Autofac等),

ASP.Net Core開始提供內建的DI Container,

並在整體專案架構設計上大量地使用DI

ConfigurationControllerView等等,
都可以看到DI的身影。

 

DI & IOC

DI使用上並不難,

難的是如何在正確的情境使用它。

DI(Denpendency Injection)中文稱依賴注入

可解決兩個類別間耦合性過高的問題,

一般會搭配介面(interface)形式進行注入,

使程式結構保有較高的彈性,

以便在需求發生變化時能夠靈活的抽換,

達到控制反轉(IOC, Inversion Of Control)的效果。

而注入的途徑有以下三種:

  • 建構式注入(Constructor Injection)
  • 方法注入(Method Injection)
  • 屬性注入(Property Injection)

相關注入範例程式如下。

建構式注入

public class Program
{
    static void Main(string[] args)
    {
        Person p = new Person("Roberson");
    }
}

public class Person
{
        private string Name { get; set; }

        public Person(string name)
        {
            this.Name = name;
        }
}

 

方法注入

public class Program
{
    static void Main(string[] args)
    {
        Person p = new Person();
        p.SetName("Roberson");
    }
}

public class Person
{
    private string Name { get; set; }

    public void SetName(string name)
    {
        this.Name = name;
    }
}

 

屬性注入

public class Program
{
    static void Main(string[] args)
    {
        Person p = new Person();
        p.Name = "Roberson";
    }
}

public class Person
{
    public string Name { get; set; }
}

大家或許都寫過類似的程式碼,

只是你並不知道這個動作其實就叫「注入」。

 

但實際的案例不會是字串這麼簡單,

下面舉個簡單的範例。

 

對部分非資訊本業的公司而言,

Excel是許多報表及資料交換的格式。

Excel格式常見的就有三種(.xls.xlsx.csv),

所對應爬取方式及套件也不同

(筆者個人使用Microsoft.Office.Interop.ExcelDocumentFormat.OpenXml、純手刻)

 

我們來寫個簡單的示意程式碼。

 

假設要爬取上面表格(.xls)中姓名的內容,

我們可以在MyExcelParser中呼叫XlsReader來幫助我們讀取Excel內容。

public class MyExcelParser
{
    private XlsTableReader xls;

    public MyExcelParser()
    {
        xls = new XlsTableReader();
    }

    public void DoParser()
    {
        string name = xls.GetCell(1, 1);
    }
}

public class XlsTableReader
{
    public string GetCell(int column, int row)
    {
        //Use Interop.Excel to get .xls cell content
        throw new NotImplementedException();
    }
}

過沒多久User又說要改成xlsx的形式,

所以我們又新增了一個XlsxTableReader.cs  

然後很自然地修改一下MyExcelParser.cs中的內容。

public class MyExcelParser
{
    //private XlsTableReader xls;
    private XlsxTableReader xlsx;

    public MyExcelParser()
    {
        xlsx = new XlsxTableReader();
    }

    public void DoParser()
    {
        string name = xlsx.GetCell(1, 1);
    }
}

public class XlsxTableReader
{
    public string GetCell(int column, int row)
    {
        //Use DocumentFormat.OpenXml to get .xlsx cell content
        throw new NotImplementedException();
    }
}

 

最後最後,User告訴你還是改成輕量的csv好了。

所以我們還要再改一次。

public class MyExcelParser
{
    //private XlsTableReader xls;
    //private XlsxTableReader xlsx;
    private CsvTableReader csv;

    public MyExcelParser()
    {
        csv = new CsvTableReader();
    }

    public void DoParser()
    {
        string name = csv.GetCell(1, 1);
    }
}

public class CsvTableReader
{
    public string GetCell(int column, int row)
    {
        //Use Custom String Split to get .csv cell content
        throw new NotImplementedException();
    }
}

 

下次User如果又想改回xls

那就又要改一次。

有什麼方式可以少改一點程式碼呢?

透過DI嗎?還不夠。

你還得搭配介面使用才能更具有彈性。

 

我們新增一個介面ITableReader.cs

public interface ITableReader
{
    string GetCell(int column, int row);
}

public class XlsTableReader : ITableReader
{
    public string GetCell(int column, int row)
    {
        //Use Interop.Excel to get .xls cell content
        throw new NotImplementedException();
    }
}

public class XlsxTableReader : ITableReader
{
    public string GetCell(int column, int row)
    {
        //Use DocumentFormat.OpenXml to get .xlsx cell content
        throw new NotImplementedException();
    }
}

public class CsvTableReader : ITableReader
{
    public string GetCell(int column, int row)
    {
        //Use Custom String Split to get .csv cell content
        throw new NotImplementedException();
    }
}

 

最後讓MyExcelParser依賴ITableReader進行建構式注入

public class MyExcelParser
{
    //private XlsTableReader xls;
    //private XlsxTableReader xlsx;
    //private CsvTableReader csv;
    private ITableReader tableReader;

    public MyExcelParser(ITableReader _tableReader)
    {
            this.tableReader = _tableReader;
    }

    public void DoParser()
    {
        string name = tableReader.GetCell(1, 1);
    }
}

 

現在我們只要改一行程式碼就可以進行切換了。

public class Program
{
    static void Main(string[] args)
    {
        //you can inject XlsTableReader, XlsxTableReader, CsvTableReader.
        MyExcelParser parser = new MyExcelParser(new XlsTableReader());
        //MyExcelParser parser = new MyExcelParser(new XlsxTableReader());
        //MyExcelParser parser = new MyExcelParser(new CsvTableReader());
    }
}

 

但倘若每個地方都要自己進行建構化注入,

管理上也是不太方便,

DI Container(Unity, Autofac等),

就是透過集中註冊方式,

幫我們統管所有DI元件的注入規則。

 

那如果不注入介面呢?

一旦注入的是實體的物件,

就失去了DI保持彈性的效果,

遇到修改時還是一樣要改一堆,

那還不如自己new一個。

 

DI好用但還是要看情境用,

並不是拿到什麼都塞進去,

針對可能抽換的元件搭配介面使用,

才能收到事半功倍的效果。

 

下一篇會講寫ASP.Net Core中DI的使用方式。

參考

https://docs.microsoft.com/zh-tw/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1