昨天我們是用範本模式簡化了流程,並且透過繼承與多型的方式去做到自訂流程範本方法
public abstract class TransactionService
{
public async Task<TransactionResult> CreateTransaction()
{
//// Get Sender Info
var bank = GetSenderInfo();
//// Set Sender Info
var httpRequestMessage = SetSenderInfo(bank);
//// Send Request
var response = await SendRequest(httpRequestMessage);
//// Handle Response
return await HandleResponse(response);
}
protected abstract Task<TransactionResult> HandleResponse(HttpResponseMessage response);
protected abstract Task<HttpResponseMessage> SendRequest(HttpRequestMessage httpRequestMessage);
protected abstract HttpRequestMessage SetSenderInfo(object bank);
protected abstract Bank GetSenderInfo();
}
接著讓我們講講工廠模式
這邊使用的是工廠方法,而非工廠物件
我們修改上面的程式為
public abstract class TransactionService
{
public async Task<TransactionResult> CreateTransaction()
{
IBankService bankService = this.GetBankService();
IBankHttpRequestBuilder httpRequestBuilder = this.GetHttpRequestBuilder();
ITransactionResultParser transactionResultParser = this.GetTransactionResultParser();
IHttpService httpService = this.GetHttpService();
//// Get Sender Info
var bank = bankService.GetSenderInfo();
//// Set Sender Info
var httpRequestMessage = httpRequestBuilder.SetSenderInfo(bank);
//// Send Request
var response = await httpService.SendRequest(httpRequestMessage);
//// Handle Response
return await transactionResultParser.HandleResponse(response);
}
protected abstract IBankService GetBankService();
protected abstract IBankHttpRequestBuilder GetHttpRequestBuilder();
protected abstract IHttpService GetHttpService();
protected abstract ITransactionResultParser GetTransactionResultParser();
}
public interface ITransactionResultParser
{
Task<TransactionResult> HandleResponse(HttpResponseMessage response);
}
public interface IHttpService
{
Task<HttpResponseMessage> SendRequest(HttpRequestMessage httpRequestMessage);
}
public interface IBankHttpRequestBuilder
{
HttpRequestMessage SetSenderInfo(Bank bank);
}
public interface IBankService
{
Bank GetSenderInfo();
}
我們將每個步驟的職責轉移到一個新物件上
當我們想要訂製流程時可以透過overload Get 方法取得不同實作,並在實作中去實作不同的內容
class SampleTransactionServiceImpl : TransactionService
{
protected override IBankService GetBankService() => new ESUNBankService();
protected override IBankHttpRequestBuilder GetHttpRequestBuilder() => new GenericBankHttpRequestBuilder();
protected override IHttpService GetHttpService() => new TaiwamBankHttpService();
protected override ITransactionResultParser GetTransactionResultParser() => new ESUNBankTransactionResultParser();
}
抽象工廠定義了生產一組相關物件的介面,透過這個介面可以拿到我們一系列相關的物件
改寫上面的程式如下:
namespace ITHome2022;
public abstract class TransactionService
{
private ITransactionFactory factory = new ESUNTransactionFactory();
public async Task<TransactionResult> CreateTransaction()
{
IBankService bankService = factory.GetBankService();
IBankHttpRequestBuilder httpRequestBuilder = factory.GetBankHttpRequestBuilder();
ITransactionResultParser transactionResultParser = factory.GetTransactionResultParser();
IHttpService httpService = factory.GetHttpService();
//// Get Sender Info
var bank = bankService.GetSenderInfo();
//// Set Sender Info
var httpRequestMessage = httpRequestBuilder.SetSenderInfo(bank);
//// Send Request
var response = await httpService.SendRequest(httpRequestMessage);
//// Handle Response
return await transactionResultParser.HandleResponse(response);
}
}
internal class ESUNTransactionFactory : ITransactionFactory
{
public IBankService GetBankService() => new ESUNBankService();
public IBankHttpRequestBuilder GetBankHttpRequestBuilder() => new ESUNBankHttpRequestBuilder();
public IHttpService GetHttpService() => new ESUNBankHttpService();
public ITransactionResultParser GetTransactionResultParser() => new ESUNBankTransactionResultParser();
}
internal interface ITransactionFactory
{
IBankService GetBankService();
IBankHttpRequestBuilder GetBankHttpRequestBuilder();
ITransactionResultParser GetTransactionResultParser();
IHttpService GetHttpService();
}
我們將物件的生產職責轉移至 ITransactionFactory
,透過替換實作來達到不依賴實際項目來取得交易所需要的資訊與流程
終於要進入重點了
其實DI跟Service Locator Pattern
非常的相像
都是先透過在容器中註冊類型,然後要使用時就能拿到對應的執行個體
我們再改寫一下上面的方法
public class TransactionService
{
private DependencyInjectionContainer _dependencyInjectionContainer;
public TransactionService(DependencyInjectionContainer dependencyInjectionContainer)
{
_dependencyInjectionContainer = dependencyInjectionContainer;
}
public async Task<TransactionResult> CreateTransaction()
{
IBankService bankService = _dependencyInjectionContainer.Get<IBankService>();
IBankHttpRequestBuilder httpRequestBuilder = _dependencyInjectionContainer.Get<IBankHttpRequestBuilder>();
ITransactionResultParser transactionResultParser = _dependencyInjectionContainer.Get<ITransactionResultParser>();
IHttpService httpService = _dependencyInjectionContainer.Get<IHttpService>();
//// Get Sender Info
var bank = bankService.GetSenderInfo();
//// Set Sender Info
var httpRequestMessage = httpRequestBuilder.SetSenderInfo(bank);
//// Send Request
var response = await httpService.SendRequest(httpRequestMessage);
//// Handle Response
return await transactionResultParser.HandleResponse(response);
}
}
public class DependencyInjectionContainer
{
public void Register<TInterface, TImplement>()
{
}
}
public static class DependencyInjectionContainerExtension
{
public static T Get<T>(this DependencyInjectionContainer dependencyInjectionContainer)
{
return default(T);
}
}
public class Program
{
async Task Main()
{
var dependencyInjectionContainer = new DependencyInjectionContainer();
dependencyInjectionContainer.Register<IBankService, ESUNBankService>();
dependencyInjectionContainer.Register<IBankHttpRequestBuilder, ESUNBankHttpRequestBuilder>();
dependencyInjectionContainer.Register<ITransactionResultParser, ESUNBankTransactionResultParser>();
dependencyInjectionContainer.Register<IHttpService, ESUNBankHttpService>();
var transactionService = new TransactionService(dependencyInjectionContainer);
await transactionService.CreateTransaction();
}
}
我們一樣註冊了介面與實作,一樣從容器取得了所需要的執行個體
什麼,我說這樣叫做DI, 你說怎麼看都是一塊塊綠豆糕 Service Locator Pattern
你沒有看錯,這樣的確是Service Locator Pattern
,而非DI
實際上的DI的寫法應該會是這樣子
public class TransactionService
{
private readonly IBankService _bankService;
private readonly IBankHttpRequestBuilder _httpRequestBuilder;
private readonly ITransactionResultParser _transactionResultParser;
private readonly IHttpService _httpService;
public TransactionService(IBankService bankService, IBankHttpRequestBuilder httpRequestBuilder, ITransactionResultParser transactionResultParser, IHttpService httpService)
{
_bankService = bankService;
_httpRequestBuilder = httpRequestBuilder;
_transactionResultParser = transactionResultParser;
_httpService = httpService;
}
public async Task<TransactionResult> CreateTransaction()
{
//// Get Sender Info
var bank = _bankService.GetSenderInfo();
//// Set Sender Info
var httpRequestMessage = _httpRequestBuilder.SetSenderInfo(bank);
//// Send Request
var response = await _httpService.SendRequest(httpRequestMessage);
//// Handle Response
return await _transactionResultParser.HandleResponse(response);
}
}
阿我們剛剛辛辛苦苦寫的DependencyInjectionContainer 去哪裡了
他其實融入了框架中
因此,當你需要使用TransactionService
他就會依照你所註冊的方式自動生出他所依賴的物件的所有執行個體
也就是上面所註冊的實作類別
ESUNBankService
ESUNBankHttpRequestBuilder
ESUNBankTransactionResultParser
ESUNBankHttpService
真是太神奇了傑克,這是怎麼做到的
別急,後面幾天會提到
雖然好像有點不一樣,可是這樣子看起來還是跟Service Locator Pattern
很像阿
其實最大的差別在於主動與被動DI
是被動地容器給你依賴項Service Locator
是主動去問容器你有沒有這個服務,有的話請給我
再舉個例子
你各位進去當兵的時候,班長會主動發給你槍,跟你說槍是你各位的第二生命,你只需要被動收好槍就行(DI)
而不是你主動去問班長我的槍呢,班長才給你槍
剛剛的DI範例其實就是建構式注入,也就是依賴項是在建構式被指派的
public class Test
{
private readonly IServiceA _serviceA;
private readonly IServiceB _serviceB;
public Test(IServiceA serviceA, IServiceB serviceB)
{
_serviceA = serviceA;
_serviceB = serviceB;
}
}
假設有兩個建構式,不同的DI Container有不同的策略,會選擇參數最多或最少的建構式注入
或是有的DI框架支援使用Attribute
public class Test
{
private readonly IServiceA _serviceA;
private readonly IServiceB _serviceB;
// 有掛attr 則使用該建構式注入對應的服務
[DependencyInjection]
public Test(IServiceA serviceA, IServiceB serviceB)
{
_serviceA = serviceA;
_serviceB = serviceB;
}
// 選擇最少則 _serviceB = null
public Test(IServiceA serviceA)
{
_serviceA = serviceA;
}
}
可以透過在屬性上標註attr. 來進行注入
在使用ServiceC不需要再額外Assign值,DI Container 會幫你準備好
public class Test
{
[DependencyInjection]
public IServiceC ServiceC { get; set; }
跟建構式注入很像只是注入的對象從建構式變成了方法
public IBankService BankService { get; set; }
public IBankHttpRequestBuilder HttpRequestBuilder { get; set; }
public ITransactionResultParser TransactionResultParser { get; set; }
public IHttpService HttpService { get; set; }
[DependencyInjection]
public async Task<TransactionResult> CreateTransaction(
IBankService bankService,
IBankHttpRequestBuilder httpRequestBuilder,
ITransactionResultParser transactionResultParser,
IHttpService httpService)
順帶一提 屬性注入 跟方法注入並不推薦使用,因為不管是他們或是Service Locator
本質上都會多一個對DI Container的依賴
也因此,.Net Core 的DI框架只提供建構式注入
IoC 跟 DI 的觀念在介紹到這邊
明天開始我們來看看dotnet core 的DI是怎麼做的,有哪些特點!