iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 8
3

Dependency Injection簡稱DI
可用於服務層抽換及撰寫測試時的接縫,
本篇將介紹DI概念及實作方式。

同步發表於個人點部落 - [鐵人賽Day08] ASP.Net Core MVC 進化之路 - Dependency Injection概念介紹

以往的專案要使用DI都需另外安裝第三方的DI Container(如UnityAutofac等),
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.Excel、DocumentFormat.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中的內容。

{
    //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 CoreDI的使用方式。

參考

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


上一篇
[鐵人賽Day07] - URL複寫(URL Rewrite)
下一篇
[鐵人賽Day09] - Dependency Injection實作
系列文
菜鳥練等區-ASP.Net Core MVC進化之路30

尚未有邦友留言

立即登入留言