Dependency Injection簡稱DI,
可用於服務層抽換及撰寫測試時的接縫,
本篇將介紹DI概念及實作方式。
以往的專案要使用DI都需另外安裝第三方的DI Container(如Unity、Autofac等),
ASP.Net Core開始提供內建的DI Container,
並在整體專案架構設計上大量地使用DI,
從Configuration、Controller、View等等,
都可以看到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.
我們來寫個簡單的示意程式碼。
假設要爬取上面表格(.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