使用具有依赖注入的策略和工厂模式
Posted
技术标签:
【中文标题】使用具有依赖注入的策略和工厂模式【英文标题】:Using a Strategy and Factory Pattern with Dependency Injection 【发布时间】:2017-02-22 20:53:14 【问题描述】:我正在从事一个辅助项目,以更好地了解控制反转和依赖注入以及不同的设计模式。
我想知道是否有将 DI 与工厂和策略模式结合使用的最佳实践?
我的挑战来自于策略(从工厂构建)需要为每个可能的构造函数和实现提供不同的参数。结果,我发现自己在服务入口点声明了所有可能的接口,并将它们传递给应用程序。因此,必须为新的和各种策略类实现更改入口点。
为了便于说明,我在下面整理了一个配对示例。我的这个项目的堆栈是 .NET 4.5/C# 和 Unity for IoC/DI。
在这个示例应用程序中,我添加了一个默认的 Program 类,它负责接受一个虚构的订单,并根据订单属性和选择的运输提供商计算运输成本。 UPS、DHL 和 Fedex 有不同的计算方式,每种实现可能依赖也可能不依赖额外的服务(访问数据库、API 等)。
public class Order
public string ShippingMethod get; set;
public int OrderTotal get; set;
public int OrderWeight get; set;
public int OrderZipCode get; set;
计算运费的虚构程序或服务
public class Program
// register the interfaces with DI container in a separate config class (Unity in this case)
private readonly IShippingStrategyFactory _shippingStrategyFactory;
public Program(IShippingStrategyFactory shippingStrategyFactory)
_shippingStrategyFactory = shippingStrategyFactory;
public int DoTheWork(Order order)
// assign properties just as an example
order.ShippingMethod = "Fedex";
order.OrderTotal = 90;
order.OrderWeight = 12;
order.OrderZipCode = 98109;
IShippingStrategy shippingStrategy = _shippingStrategyFactory.GetShippingStrategy(order);
int shippingCost = shippingStrategy.CalculateShippingCost(order);
return shippingCost;
// Unity DI Setup
public class UnityConfig
var container = new UnityContainer();
container.RegisterType<IShippingStrategyFactory, ShippingStrategyFactory>();
// also register IWeightMappingService and IZipCodePriceCalculator with implementations
public interface IShippingStrategyFactory
IShippingStrategy GetShippingStrategy(Order order);
public class ShippingStrategyFactory : IShippingStrategyFactory
public IShippingStrategy GetShippingStrategy(Order order)
switch (order.ShippingMethod)
case "UPS":
return new UPSShippingStrategy();
// The issue is that some strategies require additional parameters for the constructor
// SHould the be resolved at the entry point (the Program class) and passed down?
case "DHL":
return new DHLShippingStrategy();
case "Fedex":
return new FedexShippingStrategy();
default:
throw new NotImplementedException();
现在介绍 Strategy 接口和实现。 UPS 是一个简单的计算方法,而 DHL 和 Fedex 可能需要不同的服务(以及不同的构造函数参数)。
public interface IShippingStrategy
int CalculateShippingCost(Order order);
public class UPSShippingStrategy : IShippingStrategy()
public int CalculateShippingCost(Order order)
if (order.OrderWeight < 5)
return 10; // flat rate of $10 for packages under 5 lbs
else
return 20; // flat rate of $20
public class DHLShippingStrategy : IShippingStrategy()
private readonly IWeightMappingService _weightMappingService;
public DHLShippingStrategy(IWeightMappingService weightMappingService)
_weightMappingService = weightMappingService;
public int CalculateShippingCost(Order order)
// some sort of database call needed to lookup pricing table and weight mappings
return _weightMappingService.DeterminePrice(order);
public class FedexShippingStrategy : IShippingStrategy()
private readonly IZipCodePriceCalculator _zipCodePriceCalculator;
public FedexShippingStrategy(IZipCodePriceCalculator zipCodePriceCalculator)
_zipCodePriceCalculator = zipCodePriceCalculator;
public int CalculateShippingCost(Order order)
// some sort of dynamic pricing based on zipcode
// api call to a Fedex service to return dynamic price
return _zipCodePriceService.CacluateShippingCost(order.OrderZipCode);
上述问题在于,每种策略都需要额外的不同服务来执行“CalculateShippingCost”方法。这些接口/实现是否需要向入口点(Program 类)注册并通过构造函数向下传递?
还有其他更适合完成上述场景的模式吗?也许 Unity 可以专门处理一些事情 (https://msdn.microsoft.com/en-us/library/dn178463(v=pandp.30).aspx)?
我非常感谢任何帮助或朝着正确方向轻推。
谢谢, 安迪
【问题讨论】:
见Factory method with DI and Ioc。 【参考方案1】:有几种方法可以做到这一点,但我更喜欢的方法是将可用策略列表注入您的工厂,然后过滤它们以返回您感兴趣的策略。
使用您的示例,我将修改 IShippingStrategy
以添加新属性:
public interface IShippingStrategy
int CalculateShippingCost(Order order);
string SupportedShippingMethod get;
然后我会像这样实现工厂:
public class ShippingStrategyFactory : IShippingStrategyFactory
private readonly IEnumerable<IShippingStrategy> availableStrategies;
public ShippingStrategyFactory(IEnumerable<IShippingStrategy> availableStrategies)
this.availableStrategies = availableStrategies;
public IShippingStrategy GetShippingStrategy(Order order)
var supportedStrategy = availableStrategies
.FirstOrDefault(x => x.SupportedShippingMethod == order.ShippingMethod);
if (supportedStrategy == null)
throw new InvalidOperationException($"No supported strategy found for shipping method 'order.ShippingMethod'.");
return supportedStrategy;
我喜欢以这种方式使用它的主要原因是我永远不必回来修改工厂。如果我必须实施一项新战略,则不必更改工厂。如果您在容器中使用自动注册,您甚至不必注册新策略,所以这只是让您花更多时间编写新代码的一个例子。
【讨论】:
感谢您的帮助@John H。我为这个问题添加了一个额外的答案,并将您的答案与 Silas Reinagel 的答案结合起来。 尽管如此,string SupportedShippingMethod
似乎是一个不必要的具体实现细节恕我直言,不需要强加于策略。也许一个策略可以支持多种运输方式,或者只适用于特定尺寸的产品?更一般的做法是让每个策略以任何它想要的方式实现 bool SupportsOrder(Order)
,或者在构建工厂时将此类委托与每个策略单独传递。
@Groo 这是一个非常公平的评论。我同意这一点。【参考方案2】:
当应用依赖注入时,你将所有类的依赖定义为构造函数中的必需参数。这种做法称为Constructor Injection。这将创建依赖关系的负担从类推到了消费者身上。然而,同样的规则也适用于类的消费者。它们还需要在构造函数中定义它们的依赖关系。这会一直向上调用堆栈,这意味着所谓的“对象图”在某些时候会变得非常深。
依赖注入导致创建类的责任一直到应用程序的入口点; Composition Root。然而,这确实意味着入口点需要了解所有依赖项。如果您在没有 DI Container 的情况下使用 DI(一种称为 Pure DI 的做法),这意味着此时所有依赖项都必须使用普通的旧 C# 代码创建。如果你使用 DI 容器,你仍然需要告诉 DI 容器所有的依赖关系。
不过,有时您可以使用一种称为批处理或 自动注册 的技术,其中 DI 容器将在您的项目上使用反射并使用 Convention over Configuration 注册类型。这为您节省了逐个注册所有类型的负担,并且通常会阻止您在每次向系统添加新类时更改Composition Root。
这些接口/实现是否需要向入口点(Program 类)注册并通过构造函数向下传递?
绝对的。
因此,我发现自己在服务入口点声明了所有可能的接口,并将它们传递给应用程序。因此,必须为新的和各种策略类实现更改入口点。
应用程序的入口点是系统中最不稳定的部分(Stable-Dependencies Principle 暗示了这一点)。它总是如此,即使没有 DI。但是使用 DI,您可以使系统的其余部分变得不那么不稳定。同样,您可以通过应用自动注册来减少需要在入口点进行的代码更改量。
我想知道是否有将 DI 与工厂模式和策略模式一起使用的最佳实践?
我想说关于工厂的最佳做法是尽可能少地拥有它们,正如this article 中所解释的那样。事实上,您的工厂接口是多余的,只会使需要它的消费者复杂化(如文章中所述)。您的应用程序可以轻松完成,您可以直接注入 IShippingStrategy
,因为这是消费者唯一感兴趣的事情:获取订单的运费。它不关心它背后是否有一个或几十个实现。它只是想获得运费并继续其工作:
public int DoTheWork(Order order)
// assign properties just as an example
order.ShippingMethod = "Fedex";
order.OrderTotal = 90;
order.OrderWeight = 12;
order.OrderZipCode = 98109;
return shippingStrategy.CalculateShippingCost(order);
然而,这意味着注入的运输策略现在必须能够根据Order.Method
属性决定如何计算成本。但是有一种称为代理模式的模式。这是一个例子:
public class ShippingStrategyProxy : IShippingStrategy
private readonly DHLShippingStrategy _dhl;
private readonly UPSShippingStrategy _ups;
//...
public ShippingStrategyProxy(
DHLShippingStrategy dhl, UPSShippingStrategy ups, ...)
_dhl = dhl;
_ups = ups;
//...
public int CalculateShippingCost(Order order) =>
GetStrategy(order.Method).CalculateShippingCost(order);
private IShippingStrategy GetStrategy(string method)
switch (method)
case "DHL": return dhl;
case "UPS": return ups:
//...
default: throw InvalidOperationException(method);
这个代理在内部有点像工厂,但这里有两个重要的区别:
-
它没有定义不同的接口。这允许消费者仅依赖 1 个概念:
IShippingStrategy
。
它本身不会创建策略;它们仍然被注入其中。
此代理只是将传入调用转发到执行实际工作的底层策略实现。
有多种方法可以实现这种代理。例如,您仍然可以在此处手动创建依赖项 - 或者您可以将调用转发到容器,容器将为您创建依赖项。此外,您注入依赖项的方式可能会因最适合您的应用程序而异。
尽管这样的代理可能在内部像工厂一样工作,但重要的是这里没有工厂抽象;这只会让消费者复杂化。
Mark Seemann 和我本人在《Dependency Injection Principles, Practices, and Patterns》一书中更详细地讨论了上述所有内容。例如:
组合根在第 4.1 节中讨论, § 4.2 中的构造函数注入, 在第 6.2 节中滥用抽象工厂,以及 自动注册在第 12 章中。【讨论】:
所以我已经远离了专用的 ISomethingFactory,而更多地转向通过构造函数注入所有内容。 (复合根很有意义)。我现在正在做的是将所有“托运人”注入需要他们的班级。 public MyClass(IDictionaryLazy<>
来推迟实例的创建,如果需要的话。 Lazy<>
被一些 IoC 容器直接支持。然而,类似于抽象工厂,将Lazy<>
注入消费者是一种泄漏抽象。我会说这里提到的代理属于组合根,所以在这个代理中使用Lazy<>
是可以的。更多详情:blog.ploeh.dk/2010/01/20/EnablingDIforLazyComponents 和 blog.ploeh.dk/2014/08/24/decoraptor【参考方案3】:
所以我就这样做了。我本来希望注入一个 IDictionary,但由于将“IEnumerable”注入构造函数的限制(这个限制是 Unity 特有的),我想出了一个解决方法。
public interface IShipper
void ShipOrder(Order ord);
string FriendlyNameInstance get; /* here for my "trick" */
..
public interface IOrderProcessor
void ProcessOrder(String preferredShipperAbbreviation, Order ord);
..
public class Order
..
public class FedExShipper : IShipper
private readonly Common.Logging.ILog logger;
public static readonly string FriendlyName = typeof(FedExShipper).FullName; /* here for my "trick" */
public FedExShipper(Common.Logging.ILog lgr)
if (null == lgr)
throw new ArgumentOutOfRangeException("Log is null");
this.logger = lgr;
public string FriendlyNameInstance => FriendlyName; /* here for my "trick" */
public void ShipOrder(Order ord)
this.logger.Info("I'm shipping the Order with FedEx");
..
public class UpsShipper : IShipper
private readonly Common.Logging.ILog logger;
public static readonly string FriendlyName = typeof(UpsShipper).FullName; /* here for my "trick" */
public UpsShipper(Common.Logging.ILog lgr)
if (null == lgr)
throw new ArgumentOutOfRangeException("Log is null");
this.logger = lgr;
public string FriendlyNameInstance => FriendlyName; /* here for my "trick" */
public void ShipOrder(Order ord)
this.logger.Info("I'm shipping the Order with Ups");
..
public class UspsShipper : IShipper
private readonly Common.Logging.ILog logger;
public static readonly string FriendlyName = typeof(UspsShipper).FullName; /* here for my "trick" */
public UspsShipper(Common.Logging.ILog lgr)
if (null == lgr)
throw new ArgumentOutOfRangeException("Log is null");
this.logger = lgr;
public string FriendlyNameInstance => FriendlyName; /* here for my "trick" */
public void ShipOrder(Order ord)
this.logger.Info("I'm shipping the Order with Usps");
..
public class OrderProcessor : IOrderProcessor
private Common.Logging.ILog logger;
//IDictionary<string, IShipper> shippers; /* :( I couldn't get IDictionary<string, IShipper> to work */
IEnumerable<IShipper> shippers;
public OrderProcessor(Common.Logging.ILog lgr, IEnumerable<IShipper> shprs)
if (null == lgr)
throw new ArgumentOutOfRangeException("Log is null");
if (null == shprs)
throw new ArgumentOutOfRangeException("ShipperInterface(s) is null");
this.logger = lgr;
this.shippers = shprs;
public void ProcessOrder(String preferredShipperAbbreviation, Order ord)
this.logger.Info(String.Format("About to ship. (0)", preferredShipperAbbreviation));
/* below foreach is not needed, just "proves" everything was injected */
foreach (IShipper sh in shippers)
this.logger.Info(String.Format("ShipperInterface . (0)", sh.GetType().Name));
IShipper foundShipper = this.FindIShipper(preferredShipperAbbreviation);
foundShipper.ShipOrder(ord);
private IShipper FindIShipper(String preferredShipperAbbreviation)
IShipper foundShipper = this.shippers.FirstOrDefault(s => s.FriendlyNameInstance.Equals(preferredShipperAbbreviation, StringComparison.OrdinalIgnoreCase));
if (null == foundShipper)
throw new ArgumentNullException(
String.Format("ShipperInterface not found in shipperProviderMap. ('0')", preferredShipperAbbreviation));
return foundShipper;
...
调用代码:(例如“Program.cs”)
Common.Logging.ILog log = Common.Logging.LogManager.GetLogger(typeof(Program));
IUnityContainer cont = new UnityContainer();
cont.RegisterInstance<ILog>(log);
cont.RegisterType<IShipper, FedExShipper>(FedExShipper.FriendlyName);
cont.RegisterType<IShipper, UspsShipper>(UspsShipper.FriendlyName);
cont.RegisterType<IShipper, UpsShipper>(UpsShipper.FriendlyName);
cont.RegisterType<IOrderProcessor, OrderProcessor>();
Order ord = new Order();
IOrderProcessor iop = cont.Resolve<IOrderProcessor>();
iop.ProcessOrder(FedExShipper.FriendlyName, ord);
日志输出:
2018/09/21 08:13:40:556 [INFO] MyNamespace.Program - About to ship. (MyNamespace.Bal.Shippers.FedExShipper)
2018/09/21 08:13:40:571 [INFO] MyNamespace.Program - ShipperInterface . (FedExShipper)
2018/09/21 08:13:40:572 [INFO] MyNamespace.Program - ShipperInterface . (UspsShipper)
2018/09/21 08:13:40:572 [INFO] MyNamespace.Program - ShipperInterface . (UpsShipper)
2018/09/21 08:13:40:573 [INFO] MyNamespace.Program - I'm shipping the Order with FedEx
因此,每个具体都有一个静态字符串,以强类型的方式提供其名称。 ("FriendlyName")
然后我有一个实例 string-get 属性,它使用完全相同的值来保持同步。 ("FriendlyNameInstance")
通过使用接口上的属性强制问题(在部分代码下方)
public interface IShipper
string FriendlyNameInstance get;
我可以使用它从托运人集合中“找到”我的托运人。
内部方法“FindIShipper”是有点像工厂,但不需要有单独的 IShipperFactory 和 ShipperFactory 接口和类。从而简化了整体设置。并且仍然尊重构造函数注入和Composition root。
如果有人知道如何使用IDictionary<string, IShipper>
(并通过构造函数注入),请告诉我。
但我的解决方案有效......有点炫目。
.......................
我的第三方-dll 依赖列表。 (我使用的是 dotnet 核心,但带有半新版 Unity 的 dotnet 框架也应该可以工作)。 (参见下面的 PackageReference)
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Common.Logging" Version="3.4.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.1" />
<PackageReference Include="Unity" Version="5.8.11" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
追加:
这是 autofac 版本:
(使用上面所有相同的接口和具体)
程序.cs
namespace MyCompany.ProofOfConcepts.AutofacStrategyPatternExample.DemoCommandLineInterfaceOne
using System;
using System.Text;
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
/* need usings for all the object above */
using MyCompany.ProofOfConcepts.AutofacStrategyPatternExample.Domain;
using NLog;
using NLog.Extensions.Logging;
public class Program
private static Logger programStaticLoggerThatNeedsToBeInitiatedInMainMethod = null;
public static int Main(string[] args)
Logger loggerFromNLogLogManagerGetCurrentClassLogger = NLog.LogManager.GetCurrentClassLogger(); /* if this is made a private-static, it does not log the entries */
programStaticLoggerThatNeedsToBeInitiatedInMainMethod = loggerFromNLogLogManagerGetCurrentClassLogger;
programStaticLoggerThatNeedsToBeInitiatedInMainMethod.Info("programStaticLoggerThatNeedsToBeInitiatedInMainMethod: Main.Start");
try
bool useCodeButNotAutofacJson = true; /* true will "code up" the DI in c# code, false will kick in the autofac.json */
string autoFacFileName = useCodeButNotAutofacJson ? "autofac.Empty.json" : "autofac.json"; /* use "empty" to prove the DI is not coming from non-empty json */
programStaticLoggerThatNeedsToBeInitiatedInMainMethod.Info(string.Format("programStaticLoggerThatNeedsToBeInitiatedInMainMethod: autoFacFileName=0", autoFacFileName));
IConfiguration config = new ConfigurationBuilder()
.SetBasePath(System.IO.Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile(autoFacFileName)
.Build();
IServiceProvider servicesProvider = BuildDi(config, useCodeButNotAutofacJson);
using (servicesProvider as IDisposable)
IOrderProcessor processor = servicesProvider.GetRequiredService<IOrderProcessor>();
processor.ProcessOrder(FedExShipper.FriendlyName, new Order());
Microsoft.Extensions.Logging.ILogger loggerFromIoc = servicesProvider.GetService<ILoggerFactory>()
.CreateLogger<Program>();
loggerFromIoc.LogInformation("loggerFromIoc:Starting application");
loggerFromIoc.LogInformation("loggerFromIoc:All done!");
Console.WriteLine("Press ANY key to exit");
Console.ReadLine();
catch (Exception ex)
Console.WriteLine(GenerateFullFlatMessage(ex));
//// NLog: catch any exception and log it.
programStaticLoggerThatNeedsToBeInitiatedInMainMethod.Error(ex, "programStaticLoggerThatNeedsToBeInitiatedInMainMethod : Stopped program because of exception");
throw;
finally
// Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
LogManager.Shutdown();
Console.WriteLine("Returning 0 and exiting.");
return 0;
private static IServiceProvider BuildDi(IConfiguration config, bool useCodeButNotAutofacJson)
NLog.Extensions.Logging.NLogProviderOptions nlpopts = new NLog.Extensions.Logging.NLogProviderOptions
IgnoreEmptyEventId = true,
CaptureMessageTemplates = true,
CaptureMessageProperties = true,
ParseMessageTemplates = true,
IncludeScopes = true,
ShutdownOnDispose = true
;
IServiceCollection sc = new ServiceCollection()
////.AddLogging(loggingBuilder =>
////
//// // configure Logging with NLog
//// loggingBuilder.ClearProviders();
//// loggingBuilder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
//// loggingBuilder.AddNLog(config);
////)
.AddLogging(loggingBuilder =>
////use nlog
loggingBuilder.AddNLog(nlpopts);
NLog.LogManager.LoadConfiguration("nlog.config");
)
.AddSingleton<IConfiguration>(config);
//// // /* before autofac */ return sc.BuildServiceProvider();
//// Create a container-builder and register dependencies
Autofac.ContainerBuilder builder = new Autofac.ContainerBuilder();
// Populate the service-descriptors added to `IServiceCollection`
// BEFORE you add things to Autofac so that the Autofac
// registrations can override stuff in the `IServiceCollection`
// as needed
builder.Populate(sc);
if (useCodeButNotAutofacJson)
programStaticLoggerThatNeedsToBeInitiatedInMainMethod.Info("Coding up Autofac DI");
/* "Keyed" is not working, do not use below */
////builder.RegisterType<FedExShipper>().Keyed<IShipper>(FedExShipper.FriendlyName);
////builder.RegisterType<UpsShipper>().Keyed<IShipper>(UpsShipper.FriendlyName);
////builder.RegisterType<UspsShipper>().Keyed<IShipper>(UspsShipper.FriendlyName);
builder.RegisterType<FedExShipper>().As<IShipper>();
builder.RegisterType<UpsShipper>().As<IShipper>();
builder.RegisterType<UspsShipper>().As<IShipper>();
builder.RegisterType<OrderProcessor>().As<IOrderProcessor>();
else
programStaticLoggerThatNeedsToBeInitiatedInMainMethod.Info("Using .json file to define Autofac DI");
// Register the ConfigurationModule with Autofac.
var module = new Autofac.Configuration.ConfigurationModule(config);
builder.RegisterModule(module);
Autofac.IContainer autofacContainer = builder.Build();
// this will be used as the service-provider for the application!
return new AutofacServiceProvider(autofacContainer);
private static string GenerateFullFlatMessage(Exception ex)
return GenerateFullFlatMessage(ex, false);
private static string GenerateFullFlatMessage(Exception ex, bool showStackTrace)
string returnValue;
StringBuilder sb = new StringBuilder();
Exception nestedEx = ex;
while (nestedEx != null)
if (!string.IsNullOrEmpty(nestedEx.Message))
sb.Append(nestedEx.Message + System.Environment.NewLine);
if (showStackTrace && !string.IsNullOrEmpty(nestedEx.StackTrace))
sb.Append(nestedEx.StackTrace + System.Environment.NewLine);
if (ex is AggregateException)
AggregateException ae = ex as AggregateException;
foreach (Exception flatEx in ae.Flatten().InnerExceptions)
if (!string.IsNullOrEmpty(flatEx.Message))
sb.Append(flatEx.Message + System.Environment.NewLine);
if (showStackTrace && !string.IsNullOrEmpty(flatEx.StackTrace))
sb.Append(flatEx.StackTrace + System.Environment.NewLine);
nestedEx = nestedEx.InnerException;
returnValue = sb.ToString();
return returnValue;
........
autofac.Empty.json(设置为始终复制)
.......
autofac.json(设置为始终复制)
"defaultAssembly": "MyCompany.MyProject",
"components": [
"type": "MyCompany.MyProject.Shippers.FedExShipper",
"services": [
"type": "MyCompany.MyProject.Shippers.Interfaces.IShipper"
]
,
"type": "MyCompany.MyProject.Shippers.UpsShipper",
"services": [
"type": "MyCompany.MyProject.Shippers.Interfaces.IShipper"
]
,
"type": "MyCompany.MyProject.Shippers.UspsShipper",
"services": [
"type": "MyCompany.MyProject.Shippers.Interfaces.IShipper"
]
,
"type": "MyCompany.MyProject.Processors.OrderProcessor",
"services": [
"type": "MyCompany.MyProject.Processors.Interfaces.IOrderProcessor"
]
]
和csproj
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Autofac" Version="5.1.2" />
<PackageReference Include="Autofac.Configuration" Version="5.1.0" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.2" />
<PackageReference Include="NLog.Extensions.Logging" Version="1.6.1" />
</ItemGroup>
来自
https://autofaccn.readthedocs.io/en/latest/integration/netcore.html
PS
在 autofac 版本中,我不得不将注入的 Logger 更改为 LoggerFactory。
这是 OrderProcessor 的替代版本。您还将为所有 3 个具体的“Shipper”执行相同的“Microsoft.Extensions.Logging.ILoggerFactory loggerFactory”替代注入。
namespace MyCompany.MyProject.Processors
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
public class OrderProcessor : IOrderProcessor
////private readonly IDictionary<string, IShipper> shippers; /* :( I couldn't get IDictionary<string, IShipper> to work */
private readonly IEnumerable<IShipper> shippers;
private Microsoft.Extensions.Logging.ILogger<OrderProcessor> logger;
public OrderProcessor(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, IEnumerable<IShipper> shprs)
if (null == loggerFactory)
throw new ArgumentOutOfRangeException("loggerFactory is null");
if (null == shprs)
throw new ArgumentOutOfRangeException("ShipperInterface(s) is null");
this.logger = loggerFactory.CreateLogger<OrderProcessor>();
this.shippers = shprs;
public void ProcessOrder(string preferredShipperAbbreviation, Order ord)
this.logger.LogInformation(string.Format("About to ship. (0)", preferredShipperAbbreviation));
/* below foreach is not needed, just "proves" everything was injected */
int counter = 0;
foreach (IShipper sh in this.shippers)
this.logger.LogInformation(string.Format("IEnumerable:ShipperInterface. (0 of 1) -> (2)", ++counter, this.shippers.Count(), sh.GetType().Name));
IShipper foundShipper = this.FindIShipper(preferredShipperAbbreviation);
foundShipper.ShipOrder(ord);
private IShipper FindIShipper(string preferredShipperAbbreviation)
IShipper foundShipper = this.shippers.FirstOrDefault(s => s.FriendlyNameInstance.Equals(preferredShipperAbbreviation, StringComparison.OrdinalIgnoreCase));
if (null == foundShipper)
throw new ArgumentNullException(
string.Format("ShipperInterface not found in shipperProviderMap. ('0')", preferredShipperAbbreviation));
return foundShipper;
与 autofac 无关
nlog.config(设置为始终复制)
<?xml version="1.0" encoding="utf-8" ?>
<!-- XSD manual extracted from package NLog.Schema: https://www.nuget.org/packages/NLog.Schema-->
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xsi:schemaLocation="NLog NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
internalLogFile="MyCompany.MyProject.Nlog.internalLogFile.log"
internalLogLevel="Info" >
<!-- the targets to write to -->
<targets>
<!-- write logs to file -->
<target xsi:type="File" name="target1" fileName="MyCompany.MyProject.Nlog.MyConsoleAppProgram.log"
layout="$date|$level:uppercase=true|$message $exception|$logger|$all-event-properties" />
<target xsi:type="Console" name="target2"
layout="$date|$level:uppercase=true|$message $exception|$logger|$all-event-properties" />
</targets>
<!-- rules to map from logger name to target -->
<rules>
<logger name="*" minlevel="Trace" writeTo="target1,target2" />
</rules>
</nlog>
【讨论】:
Java 版本在这里***.com/questions/52279459/… 和这里***.com/questions/52338322/…【参考方案4】:使用您的策略类型字符串注册并解决它们。
像这样:
// Create container and register types
IUnityContainer myContainer = new UnityContainer();
myContainer.RegisterType<IShippingStrategy, FedexShippingStrategy>("Fedex");
myContainer.RegisterType<IShippingStrategy, DHLShippingStrategy>("DHL");
// Retrieve an instance of each type
IShippingStrategy shipping = myContainer.Resolve<IShippingStrategy>("DHL");
IShippingStrategy shipping = myContainer.Resolve<IShippingStrategy>("Fedex");
【讨论】:
感谢您的帮助@Silas Reinagel。我在 John H 的帖子中添加了一些细节以及您的回答。我很感激。 这仍然是“服务定位器”而不是“Ioc/DI”。它有效……但考虑到它不是纯 DI。 @granadaCoder 完全同意。事实上,任何涉及解析工具的事情本身都与 DI/IoC 无关。 DI/IoC 是架构方法,而不是工具。是否使用服务定位器模式是一个问题,即对象本身是否被请求特定实例,或者组合根是否选择将哪个依赖注入到对象中。【参考方案5】:请参阅 John H 和 Silas Reinagel 的答案。他们都很有帮助。
我最终做了两个答案的组合。
我更新了 John H 提到的工厂和界面。
然后在 Unity 容器中,我添加了具有新命名参数的实现,例如 Silas Reinagel 展示的。
然后我按照这里的答案使用 Unity 注册集合以注入策略工厂。 Way to fill collection with Unity
现在每个策略都可以单独实现,无需修改上游。
谢谢大家。
【讨论】:
以上是关于使用具有依赖注入的策略和工厂模式的主要内容,如果未能解决你的问题,请参考以下文章