問題描述
考慮以下策略模式的實作:
// this is the strategy definition
public interface ICalculator
{
int ComputeResult(int a, int b);
}
// this is an implementation of the strategy
public sealed class SumCalculator: ICalculator
{
public int ComputeResult(int a, int b) => a b;
}
// this is another implementation of the strategy
public sealed class SubtractionCalculator: ICalculator
{
public int ComputeResult(int a, int b) => a - b;
}
假設我們需要為ICalculator
服務撰寫一些客戶端代碼。客戶端代碼得到以下輸入資料:
- 一個整數,通過變數
a
- 另一個整數,通過變數
b
- 用于決定需要使用哪種策略的背景關系資訊。讓我們假設有一個名為的列舉
TaskType
,其可能值為Sum
和Subtract
。
從功能的角度來看,客戶端代碼應該做這樣的事情:
int a = GetFirstOperand();
int b = GetSecondOperand();
TaskType taskType = GetTaskType();
ICalculator calculator = null;
switch(taskType)
{
case TaskType.Sum:
calculator = new SumCalculator();
break;
case TaskType.Subtract:
calculator = new SubtractionCalculator();
break;
default:
throw new NotSupportedException($"Task type {taskType} is not supported");
}
int result = calculator.ComputeResult(a,b);
Console.Writeline($"The result is: {result}");
現在考慮一個使用依賴注入并將物件創建和生命周期管理委托給 DI 容器的代碼庫。在這種情況下,ICalculator
服務的客戶端代碼不能直接負責創建物件。
我試圖找到的基本上是一種解決這個問題的優雅而有效的方法。
在這種情況下我通常會做什么
這是我通常用來解決這個問題的方法。我將這種設計模式稱為復合設計模式,但我很確定這不完全是四本書中名為復合設計模式的模式。
首先,ICalculator
需要對界面進行重塑(稍后會詳細介紹):
public interface ICalculator
{
int ComputeResult(int a, int b, TaskType taskType);
bool CanHandleTask(TaskType taskType);
}
現有的介面實作需要改變:
public sealed class SumCalculator: ICalculator
{
public int ComputeResult(int a, int b, TaskType taskType)
{
if (!this.CanHandleTask(taskType))
{
throw new InvalidOperationException($"{nameof(SumCalculator)} cannot handle task {taskType}");
}
return a b;
}
public bool CanHandleTask(TaskType taskType) => taskType == TaskType.Sum;
}
public sealed class SubtractionCalculator: ICalculator
{
public int ComputeResult(int a, int b, TaskType taskType)
{
if (!this.CanHandleTask(taskType))
{
throw new InvalidOperationException($"{nameof(SubtractionCalculator)} cannot handle task {taskType}");
}
return a - b;
}
public bool CanHandleTask(TaskType taskType) => taskType == TaskType.Subtract;
}
ICalculator
需要撰寫介面的第三個實作。我稱這個物件為復合物件:
public sealed class CompositeCalculator: ICalculator
{
private readonly IEnumerable<ICalculator> _calculators;
public CompositeCalculator(IEnumerable<ICalculator> calculators)
{
_calculators = calculators ?? throw new ArgumentNullException(nameof(calculators));
}
public int ComputeResult(int a, int b, TaskType taskType)
{
if (!this.CanHandleTask(taskType))
{
throw new InvalidOperationException($"{nameof(CompositeCalculator)} cannot handle task {taskType}");
}
var handler = _calculators.First(x => x.CanHandleTask(taskType));
return handler.ComputeResult(a, b, taskType);
}
public bool CanHandleTask(TaskType taskType) => _calculators.Any(x => x.CanHandleTask(taskType));
}
這是客戶端代碼ICalculator
:
// this class encapsulates the client code of ICalculator
public sealed class AnotherService
{
private readonly ICalculator _calculator;
public AnotherService(ICalculator calculator)
{
_calculator = calculator ?? throw new ArgumentNullException(nameof(calculator));
}
public void DoSomething()
{
// code omitted for brevity
int a = ...;
int b = ...;
TaskType taskType = ...;
var result = _calculator.ComputeResult(a, b, taskType);
Console.Writeline($"The result is {result}");
}
}
最后,這里是ICalculator
介面在DI容器中的注冊:
services.AddSingleton<SumCalculator>();
services.AddSingleton<SubtractionCalculator>();
services.AddSingleton<ICalculator>(sp =>
{
var calculators = new List<ICalculator>
{
sp.GetRequiredService<SumCalculator>(),
sp.GetRequiredService<SubtractionCalculator>()
};
return new CompositeCalculator(calculators);
});
這種模式有效,但我不喜歡ICalculator
需要修改介面才能將CanHandleTask
方法和無關引數taskType
引入方法的事實ComputeResult
。
介面的原始定義(參見上面的問題描述)對于能夠使用兩個整數作為計算輸入來計算結果的服務ICalculator
來說似乎是一個更自然的定義。
替代解決方案
這個問題的另一種解決方案是為ICalculator
介面引入一個工廠物件。這有點類似于IHttpClientFactory
.NET core 中引入的介面。
這樣我們就可以保留ICalculator
介面的原始定義:
public interface ICalculator
{
int ComputeResult(int a, int b);
}
我們需要為ICalculator
實體引入一個工廠物件:
public interface ICalculatorFactory
{
ICalculator CreateCalculator(TaskType taskType);
}
這些是ICalculator
介面的實作(不再需要復合物件):
public sealed class SumCalculator: ICalculator
{
public int ComputeResult(int a, int b) => a b;
}
public sealed class SubtractionCalculator: ICalculator
{
public int ComputeResult(int a, int b) => a - b;
}
這是客戶端代碼的新版本:
// this class encapsulates the client code of ICalculator
public sealed class AnotherService
{
private readonly ICalculatorFactory _calculatorFactory;
public AnotherService(ICalculatorFactory calculatorFactory)
{
_calculatorFactory = calculatorFactory ?? throw new ArgumentNullException(nameof(calculatorFactory));
}
public void DoSomething()
{
// code omitted for brevity
int a = ...;
int b = ...;
TaskType taskType = ...;
var calculator = _calculatorFactory.CreateCalculator(taskType);
var result = calculator.ComputeResult(a, b);
Console.Writeline($"The result is {result}");
}
}
介面的具體實作ICalculatorFactory
將物件創建委托給 DI 容器,并在組合根內部定義(因為它直接依賴于 DI 容器):
public sealed class ServiceProviderCalculatorFactory: ICalculatorFactory
{
private readonly IServiceProvider _serviceProvider;
public ServiceProviderCalculatorFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
}
public ICalculator CreateCalculator(TaskType taskType)
{
switch(taskType)
{
case TaskType.Sum:
return _serviceProvider.GetRequiredService<SumCalculator>();
case TaskType.Subtract:
return _serviceProvider.GetRequiredService<SubtractionCalculator>();
default:
throw new NotSupportedException($"Task type {taskType} is not supported");
}
}
}
最后,這里是 DI 容器上的服務注冊:
services.AddSingleton<SumCalculator>();
services.AddSingleton<SubtractionCalculator>();
services.AddSingleton<ICalculatorFactory, ServiceProviderCalculatorFactory>();
這種解決方案的主要優點是避免了CanHandle
上述復合模式的所有儀式。
問題
有沒有更好或規范的方法來解決這個問題?
uj5u.com熱心網友回復:
現在考慮一個使用依賴注入并將物件創建和生命周期管理委托給 DI 容器的代碼庫。
可以通過建構式注入依賴。讓我舉個例子。
我稍微改了名字。所以現在列舉看起來像這樣:
public enum OperationType
{
Sum,
Subtract
}
這是你的抽象:
public interface IOperation
{
int Compute(int a, int b);
}
及其具體實作:
public class SumOperation : IOperation
{
public int Compute(int a, int b) => a b;
}
public class SubtractionOperation : IOperation
{
public int Compute(int a, int b) => a - b;
}
然后我們通過建構式注入所有依賴項,以允許未來的客戶使用按操作型別CalculatorOperationFactory
的具體實作:IOperation
public class CalculatorOperationFactory
{
private Dictionary<OperationType, IOperation> _operationByType;
public CalculatorOperationFactory(SumOperation sumOperation,
SubtractionOperation subtractionOperation)
{
_operationByType = new Dictionary<OperationType, IOperation>()
{
{ OperationType.Sum, sumOperation },
{ OperationType.Subtract, subtractionOperation },
};
}
public IOperation GetInstanceByOperationType(OperationType taskType)
=> _operationByType[taskType];
}
然后你可以CalculatorOperationFactory
注入AnotherService
:
public class AnotherService
{
CalculatorOperationFactory _calculatorFactory;
public AnotherService(CalculatorOperationFactory calculatorFactory)
{
_calculatorFactory = calculatorFactory;
}
public void DoSomething()
{
// code is omitted for brevity
int a = 0;
int b = 1;
OperationType taskType = OperationType.Sum;
IOperation operation = _calculatorFactory
.GetInstanceByTaskType(taskType);
var result = operation.Compute(a, b);
}
}
您的依賴項將如下所示:
services.AddSingleton<SumOperation>();
services.AddSingleton<SubtractionOperation>();
services.AddSingleton<CalculatorFactory>();
uj5u.com熱心網友回復:
可能是第一個解決方案的輕微變化:
public interface ICalculatorStrategy
{
int ComputeResult(int a, int b);
TaskType TaskType { get; }
}
public class SumCalculator : ICalculatorStrategy
{
public int ComputeResult(int a, int b) => a b;
public TaskType TaskType => TaskType.Sum;
}
public sealed class SubtractionCalculator: ICalculatorStrategy
{
public int ComputeResult(int a, int b) => a - b;
public TaskType TaskType => TaskType.Subtract;
}
public interface ICalculator
{
public int ComputeResult(int a, int b, TaskType type);
}
public class Calculator : ICalculator
{
private readonly IEnumerable<ICalculatorStrategy> _strategies;
public Calculator(IEnumerable<ICalculatorStrategy> strategies) =>
_strategies = strategies;
public int ComputeResult(int a, int b, TaskType type)
{
var strategy = _strategies.FirstOrDefault(s => s.TaskType == type)
?? throw new InvalidOperationException($"No strategy found for type {type}");
return strategy.ComputeResult(a, b);
}
}
MS DI 容器可以為您注入IEnumerable<ICalculatorStrategy>
。
services.AddTransient<ICalculatorStrategy, SumCalculator>();
services.AddTransient<ICalculatorStrategy, SubtractionCalculator>();
services.AddTransient<ICalculator, Calculator>();
uj5u.com熱心網友回復:
我認為第二種方法非常好。您可以處理它的另一種方法是切換到支持鍵控依賴項的容器,Autofac
并使用它來生成Func
工廠,就像幾年前創建的這個片段一樣:
var builder = new ContainerBuilder();
builder.RegisterType<ImplOne>()
.Keyed<IDependency>(MyTypeEnum.TypeOne)
.SingleInstance();
builder.RegisterType<ImplTwo>()
.Keyed<IDependency>(MyTypeEnum.TypeTwo)
.SingleInstance();
builder.Register((c, p) =>
{
var type = p.TypedAs<MyTypeEnum>();
var resolve = c.Resolve<IIndex<MyTypeEnum, IDependency>>();
return resolve[type];
});
var container = builder.Build();
Func<MyTypeEnum, IDependency> factory = container.Resolve<Func<MyTypeEnum, IDependency>>();
var dependency = factory(MyTypeEnum.TypeOne);
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/508234.html
標籤:C# asp.net 核心 设计模式 依赖注入 ioc 容器
下一篇:發布后如何更新模型屬性值?