web-dev-qa-db-ja.com

依存性注入による戦略とファクトリパターンの使用

私は、制御の反転と依存性注入、およびさまざまな設計パターンをよりよく理解するために、サイドプロジェクトに取り組んでいます。

ファクトリパターンと戦略パターンでDIを使用するためのベストプラクティスがあるかどうか疑問に思っています

私の挑戦は、(ファクトリーから構築された)戦略が、可能な各コンストラクターと実装に異なるパラメーターを必要とするときに発生します。その結果、サービスエントリポイントですべての可能なインターフェイスを宣言し、それらをアプリケーションに渡します。その結果、新規およびさまざまな戦略クラスの実装のためにエントリポイントを変更する必要があります。

以下の説明のために、ペアになった例をまとめました。このプロジェクトの私のスタックは.NET 4.5/C#とIoC/DIのUnityです。

このサンプルアプリケーションでは、架空の注文を受け付け、注文のプロパティと選択した配送業者に応じて送料を計算するデフォルトの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 )?

私は、正しい方向への助けやナッジに大いに感謝しています。

ありがとう、アンディ

10
apleroy

これを行うにはいくつかの方法がありますが、私が好む方法は、利用可能な戦略のリストをファクトリに挿入し、それらをフィルタリングして、興味のある戦略を返すことです。

例を使用して、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;
    }
}

このように使用するのが好きな主な理由は、工場に戻って工場を改造する必要がないからです。新しい戦略を実装する必要がある場合でも、ファクトリーを変更する必要はありません。コンテナーで自動登録を使用している場合は、新しい戦略を登録する必要もないため、新しいコードの作成により多くの時間を費やすことができます。

12
John H

Dependency Injectionを適用する場合、すべてのクラスの依存関係をコンストラクターの必須引数として定義します。この方法はConstructor Injectionと呼ばれます。これにより、依存関係を作成する負担がクラスからそのコンシューマにかかります。ただし、同じ規則がクラスの消費者にも適用されます。また、コンストラクターで依存関係を定義する必要もあります。これは、コールスタック全体に渡って行きます。これは、いわゆる「オブジェクトグラフ」がいくつかの点でかなり深くなる可能性があることを意味します。

依存性注入は、アプリケーションのエントリポイントまでのクラスを作成する責任を引き起こします。 構成ルート 。ただし、これは、エントリポイントがすべての依存関係を認識する必要があることを意味します。 DIコンテナー ----純粋なDI-と呼ばれる手法を使用しない場合、この時点で、すべての依存関係をプレーンな古いC#コードで作成する必要があります。 DIコンテナーを使用する場合でも、すべての依存関係についてDIコンテナーに通知する必要があります。

ただし、バッチまたはAuto-Registrationと呼ばれる手法を利用できる場合もあります。この場合、DIコンテナーはプロジェクトに対してリフレクションを使用し、 Convention over Configuration を使用してタイプを登録します。これにより、すべてのタイプを1つずつ登録する負担が軽減され、新しいクラスがシステムに追加されるたびに Composition Root に変更を加えることができなくなります。

これらのインターフェース/実装は、エントリポイント(Programクラス)に登録し、コンストラクターに渡す必要がありますか?

もちろんです。

その結果、サービスエントリポイントですべての可能なインターフェイスを宣言し、それらをアプリケーションに渡します。その結果、新規およびさまざまな戦略クラスの実装のためにエントリポイントを変更する必要があります。

アプリケーションのエントリポイントは、システムの最も揮発性の高い部分です。 DIがなくても常にそうです。しかし、DIを使用すると、システムの残りの部分の揮発性が大幅に低下します。繰り返しになりますが、Auto-Registrationを適用することで、エントリポイントで行う必要があるコード変更の量を減らすことができます。

ファクトリパターンと戦略パターンでDIを使用するためのベストプラクティスがあるかどうか疑問に思っていますか?

この記事 で説明されているように、工場に関するベストプラクティスは、それらをできるだけ少なくすることです。実際のところ、ファクトリインターフェースは冗長であり、それを必要とするコンシューマーを複雑にするだけです(記事で説明しています)。これは消費者が関心を持っている唯一のことなので、アプリケーションを使用せずに簡単に行うことができ、代わりに直接IShippingStrategyを注入できます。注文の送料を取得するためです。その背後にある実装が1つであるか数十であるかは関係ありません。それはちょうど送料を取得してその仕事を続けたいと思っています:

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

このプロキシは内部的にはファクトリのように機能しますが、ここには2つの重要な違いがあります。

  1. 別のインターフェースを定義するものではありません。これにより、消費者はIShippingStrategyという1つの概念にのみ依存することができます。
  2. 戦略自体は作成されません。彼らはまだそれに注入されています。

このプロキシは、着信コールを、実際の作業を行う基盤となる戦略実装に転送するだけです。

このようなプロキシを実装するには、さまざまな方法があります。たとえば、ここで手動で依存関係を作成することも、依存関係を作成するコンテナに呼び出しを転送することもできます。また、依存関係を挿入する方法は、アプリケーションに最適なものによって異なる場合があります。

そして、そのようなプロキシは内部的にはファクトリのように機能するかもしれませんが、重要なことは、ここにファクトリAbstractionがないことです。それは消費者を複雑にするだけです。

上記のすべては、Mark Seemannと私自身による本 Dependency Injection Principles、Practices、and Patterns で詳しく説明されています。たとえば、Composition Rootは§4.1で、Constructor Injectionは§4.2で、抽象ファクトリの乱用は§6.2で、そしてAuto-Registrationについて説明します。 12章で。

17
Steven

だから私はこのようにしました。私は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

そのため、各コンクリートには、厳密に型指定された方法で名前を提供する静的文字列があります。 ("分かりやすい名前")

そして、完全に同じ値を使用して同期を保つインスタンスのstring-getプロパティがあります。 ( "FriendlyNameInstance")

インターフェイスのプロパティを使用して問題を強制する(部分的なコードの下)

public interface IShipper
{
   string FriendlyNameInstance { get;}
}

これを使用して、荷送人のコレクションから荷送人を「見つける」ことができます。

内部メソッドの "هShipper"は少しファクトリーですが、個別のIShipperFactoryおよびShipperFactoryインターフェイスとクラスを用意する必要がなくなります。したがって、全体的なセットアップが簡素化されます。それでも、コンストラクタインジェクションと コンポジションルート を尊重します。

誰かがIDictionary<string, IShipper>(およびコンストラクターを介して注入)、私に知らせてください。

しかし、私の解決策は動作します...

...........................

私のサードパーティ-dll依存関係リスト。 (私はドットネットコアを使用していますが、Unityの新しいバージョンのドットネットフレームワークも動作するはずです)。 (下記の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バージョンでは、注入されるロガーを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>
3
granadaCoder

ストラテジータイプ文字列を使用してそれらを登録および解決します。

このような:

// 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");
2
Silas Reinagel

John HとSilas Reinagelの両方の回答をご覧ください。どちらも非常に役に立ちます。

私は両方の答えを組み合わせてやってしまいました。

John Hが言及するように、ファクトリーとインターフェースを更新しました。

そして、Unityコンテナーに、Silas Reinagelが示すように、新しい名前付きパラメーターを使用して実装を追加しました。

次に、Unityを使用してコレクションを戦略ファクトリーに注入するために登録するために、ここで答えに従いました。 nityでコレクションを埋める方法

これで、アップストリームを変更する必要なく、各戦略を個別に実装できます。

皆さん、ありがとうございました。

2
apleroy