使用策略模式和工厂模式结合依赖注入技术

30
我正在进行一个副项目,以更好地理解控制反转和依赖注入以及不同的设计模式。
我想知道是否有使用工厂和策略模式的依赖注入的最佳实践?
我的挑战在于,当一个策略(由工厂构建)需要每个可能的构造函数和实现的不同参数时。因此,我发现自己在服务入口点中声明了所有可能的接口,并将它们通过应用程序传递下去。结果,入口点必须针对新的和各种策略类实现进行更改。
我已经为说明目的准备了一个简化的示例应用程序。我的堆栈是.NET 4.5 / C#和Unity用于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(); 
        }
    }
}

现在来看策略接口和实现。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)?
我非常感谢任何帮助或指引。
谢谢, Andy

1
请查看使用DI和Ioc的工厂方法 - NightOwl888
5个回答

45

有几种方法可以做到这一点,但我更喜欢的方法是将可用策略列表注入到工厂中,然后进行过滤以返回您感兴趣的策略。

根据您的示例,我会修改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的答案结合起来。 - apleroy
8
不过,string SupportedShippingMethod 在我看来似乎是一个不必要的具体实现细节,不需要强加在策略上。也许一种策略可以支持多种运输方式,或仅适用于特定尺寸的产品?更普遍的做法是,要么让每个策略以任何想要的方式实现 bool SupportsOrder(Order),要么在构建工厂时将这样的委托与每个策略分开传递。 - vgru
@Groo 这是一个相当公正的评论。我同意。 - John H

23
当应用依赖注入时,你需要在构造函数中定义所有类的依赖关系作为必需参数。这种做法被称为构造函数注入。这将把创建依赖项的负担从类推到其使用者身上。然而,同样的规则也适用于类的使用者。他们也需要在其构造函数中定义它们的依赖项。这一直延伸到整个调用堆栈,这意味着所谓的“对象图”在某些点可能变得非常深。

依赖注入会导致负责创建类一直延伸到应用程序的入口点,即组合根。然而,这也意味着入口点需要知道所有的依赖项。如果您在不使用DI容器的情况下使用DI(称为纯DI),则这意味着此时必须在普通的C#代码中创建所有依赖项。如果您使用DI容器,则仍然必须告诉DI容器有关所有依赖项。

有时,您可以利用一种称为批处理或自动注册的技术,在这种情况下,DI容器将使用反射来遍历您的项目,并使用约定优于配置注册类型。这样可以减轻您逐个注册所有类型的负担,并且通常可以防止每次向系统添加新类时更改组合根。

这些接口/实现是否需要在入口点(Program类)中进行注册并通过构造函数传递?

绝对需要。

因此,我发现自己在服务入口点中声明所有可能的接口,并通过应用程序将它们传递下去。结果,入口点必须更改以适应新的和不同的策略类实现。

该应用程序的入口点是系统中最不稳定的部分(这暗示了稳定依赖原则)。即使没有 DI,它始终如此。但是使用 DI,您可以使系统的其余部分变得不那么不稳定。同样,您可以通过应用自动注册来减少在入口点需要进行的代码更改量。

我想知道在工厂模式和策略模式中使用 DI 是否有最佳实践?

我认为关于工厂的最佳实践是尽可能少地使用它们,正如 这篇文章 中所解释的那样。事实上,您的工厂接口是多余的,并且只会使需要它的消费者变得更加复杂(正如文章中所解释的那样)。您的应用程序可以轻松地不用它,而是直接注入一个 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
  2. 它不会自己创建策略; 它们仍然被注入到其中。

这个代理只是将传入的调用转发到执行实际工作的底层策略实现。

有许多方法可以实现这样的代理。例如,您仍然可以手动在此处创建依赖项-或者您可以将调用转发到容器,由容器为您创建依赖关系。另外,您注入依赖项的方式可能因应用程序而异。

即使这样的代理在内部工作方式类似于工厂,重要的是这里没有工厂抽象;这只会使消费者复杂化。

上述所有内容都在 Dependency Injection Principles, Practices, and Patterns 这本书中详细讨论,作者是Mark Seemann和我。

  • §4.1讨论了组合根(Composition Root)
  • §4.2讨论了构造函数注入(Constructor Injection)
  • §6.2讨论了抽象工厂(Abstract Factories)的滥用
  • 第12章讨论了自动注册(Auto-Registration)

所以我已经摆脱了专用的 ISomethingFactory,更倾向于通过构造函数注入所有内容(组合根非常合理)。现在我正在将所有“发货人”注入到需要它们的类中。public MyClass(IDictionary<string, IShipper> availableShippers),并有一个迷你私有方法来根据字符串键“查找”我需要的那个。实际上,我在Java中也有同样的伪示例(见下一条评论)。 - granadaCoder
Guice版本:https://stackoverflow.com/questions/52279459/guice-with-multiple-concretes-picking-one-of-them/52283452#52283452 和Spring版本:https://stackoverflow.com/questions/52338322/spring-di-beans-with-multiple-concretes-picking-one-of-them/52341778#52341778 - granadaCoder
1
我喜欢这种方法,但是不会导致很多从未使用的实例吗? - IngoB
2
@IngoB 您可以使用 Lazy<> 来延迟实例的创建,如果它们需要时才会被使用。 Lazy<> 直接受到一些 IoC 容器的支持。然而,类似于抽象工厂,将 Lazy<> 注入到消费者中有点像是一个泄漏的抽象。我认为这里提到的代理属于组合根,因此在此代理中使用 Lazy<> 是可以的。更多细节请参见:http://blog.ploeh.dk/2010/01/20/EnablingDIforLazyComponents/ 和 http://blog.ploeh.dk/2014/08/24/decoraptor/ - Creepin
1
你说最佳实践是尽可能少地使用工厂并链接到一篇文章,然而在这篇文章中它说“工厂是可以的”,通常应该避免在LOB应用程序中使用抽象工厂。 - Ronnie
显示剩余2条评论

5

所以我是这样做的。我本来更喜欢注入一个IDictionary<string, IShipper>,但由于将“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")

然后,我有一个实例字符串获取属性,它使用完全相同的值来保持同步。("FriendlyNameInstance")

通过在接口上使用属性来强制执行此问题(下面是部分代码)

public interface IShipper
{
   string FriendlyNameInstance { get;}
}

我可以使用这个方法从承运人集合中“查找”我的承运人。
内部方法“FindIShipper”是一种工厂方法,但它消除了需要单独的IShipperFactory和ShipperFactory接口和类的需求。从而简化了整体设置。仍然遵守构造函数注入和组合根
如果有人知道如何使用IDictionary<string, IShipper>(并通过构造函数进行注入),请告诉我。
但我的解决方案可行...只需一点点把戏就行了。

我的第三方dll依赖列表。(我正在使用dotnet core,但是使用半新版本的Unity的dotnet framework也可以。) (请参见下面的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 版本:

(使用上述所有相同的接口和具体类)

Program.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>

2022年底追加。

如果您需要在多个地方使用以下代码功能(以下代码已从上面复制)...

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;
}

那么你应该(避免复制和粘贴),并且(更喜欢)将代码封装到自己的“工厂”中。

如果您按照下面的SOF答案链接..它将展示创建“工厂”的基础知识。 (对于这个答案,您将拥有一个IShipperFactory..在下面的答案中被称为“IDbContextsFactory”)。 但是,思路是相同的。

https://stackoverflow.com/a/74391698/214977


Java版本在此处提供:https://stackoverflow.com/questions/52279459/guice-with-multiple-concretes-picking-one-of-them/52283452#52283452和https://stackoverflow.com/questions/52338322/spring-di-beans-with-multiple-concretes-picking-one-of-them/52341778#52341778。 - granadaCoder

2
请参考John H和Silas Reinagel的答案。他们都非常有帮助。
我最终采用了两个答案的结合。
我像John H提到的那样更新了工厂和接口。
然后在Unity容器中,我添加了使用新命名参数的实现,就像Silas Reinagel所示。
然后我遵循这里的答案,使用Unity注册集合以便注入到策略工厂中。 使用Unity填充集合的方法 现在每个策略可以单独实现,而无需修改上游。
谢谢大家。

2

使用您的策略类型字符串注册并解决它们。

就像这样:

// 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帖子中的一些细节。非常感谢。 - apleroy
1
这仍然是“服务定位器”,而不是“Ioc / DI”。它可以工作...但请注意它不是纯DI。 - granadaCoder
@granadaCoder 完全同意。实际上,任何涉及解析工具的事情都与 DI/IoC 本身无关。DI/IoC 是架构方法,而不是工具。是否使用服务定位器模式取决于对象是否请求特定实例,或者组合根据选择哪个依赖项注入到对象中。 - Silas Reinagel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接