Using a Strategy and Factory Pattern with Dependency Injection

后端 未结 5 420
有刺的猬
有刺的猬 2020-12-05 15:50

I am working on a side project to better understand Inversion of Control and Dependency Injection and different design patterns.

I am wondering if there are

5条回答
  •  無奈伤痛
    2020-12-05 16:04

    So I did it like this. I would have preferred to have injected an IDictionary, but because of the limitation with injecting "IEnumerable" into the constructor (this limitation is Unity specific), I came up with a little workaround.

    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 shippers; /*   :(    I couldn't get IDictionary  to work */
        IEnumerable shippers;
    
        public OrderProcessor(Common.Logging.ILog lgr, IEnumerable 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;
        }
    }
    

    ...

    And calling code: (that would be in something like "Program.cs" for example)

                Common.Logging.ILog log = Common.Logging.LogManager.GetLogger(typeof(Program));
    
                IUnityContainer cont = new UnityContainer();
    
                cont.RegisterInstance(log);
    
                cont.RegisterType(FedExShipper.FriendlyName);
                cont.RegisterType(UspsShipper.FriendlyName);
                cont.RegisterType(UpsShipper.FriendlyName);
    
                cont.RegisterType();
    
                Order ord = new Order();
                IOrderProcessor iop = cont.Resolve();
                iop.ProcessOrder(FedExShipper.FriendlyName, ord);
    

    Logging Output:

    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
    

    So each concrete has a static string providing its name in a strong-typed fashion. ("FriendlyName")

    And then I have an instance string-get property which uses the exact same value to keep things in sync. ("FriendlyNameInstance")

    By forcing the issue by using a property on the Interface (below partial code)

    public interface IShipper
    {
       string FriendlyNameInstance { get;}
    }
    

    I can use this to "find" my shipper out of the collection of shippers.

    The internal method "FindIShipper" is the kinda-factory, but removes the need to have a separate IShipperFactory and ShipperFactory interface and class. Thus simplifying the overall setup. And still honors Constructor-Injection and Composition root.

    If anyone knows how to use IDictionary (and inject via the constructor), please let me know.

    But my solution works...with a little razzle dazzle.

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

    My third-party-dll dependency list. (I'm using dotnet core, but dotnet framework with a semi new version of Unity should work too). (See PackageReference's below)

    
    
      
        Exe
        netcoreapp2.0
      
    
      
        
        
        
        
        
      
    
      
        
          Always
        
      
    
    
    

    APPEND:

    Here is the autofac version:

    (using all the same interfaces and concretes above )

    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();
                        processor.ProcessOrder(FedExShipper.FriendlyName, new Order());
    
                        Microsoft.Extensions.Logging.ILogger loggerFromIoc = servicesProvider.GetService()
                        .CreateLogger();
                        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(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().Keyed(FedExShipper.FriendlyName);
                    ////builder.RegisterType().Keyed(UpsShipper.FriendlyName);
                    ////builder.RegisterType().Keyed(UspsShipper.FriendlyName);
    
                    builder.RegisterType().As();
                    builder.RegisterType().As();
                    builder.RegisterType().As();
                    builder.RegisterType().As();
                }
                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 (set to copy always)

    {}
    

    .......

    autofac.json (set to copy always)

    {
      "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"
            }
          ]
        }
      ]
    }
    

    and csproj

      
        Exe
        netcoreapp3.1
      
    
      
        
        
        
        
        
        
        
        
        
        
      
    

    From

    https://autofaccn.readthedocs.io/en/latest/integration/netcore.html

    PS

    In the autofac version I had to change the Logger being injected to be a LoggerFactory.

    Here is the OrderProcessor alternate version. You'll have do the same "Microsoft.Extensions.Logging.ILoggerFactory loggerFactory" alternative injection for all 3 concrete "Shipper"'s as well.

    namespace MyCompany.MyProject.Processors
    {
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using Microsoft.Extensions.Logging;
        public class OrderProcessor : IOrderProcessor
        {
            ////private readonly IDictionary shippers; /*   :(    I couldn't get IDictionary  to work */
            private readonly IEnumerable shippers;
            private Microsoft.Extensions.Logging.ILogger logger;
    
            public OrderProcessor(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, IEnumerable shprs)
            {
                if (null == loggerFactory)
                {
                    throw new ArgumentOutOfRangeException("loggerFactory is null");
                }
    
                if (null == shprs)
                {
                    throw new ArgumentOutOfRangeException("ShipperInterface(s) is null");
                }
    
                this.logger = loggerFactory.CreateLogger();
                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;
            }
        }
    }
    

    not related to autofac

    nlog.config (set to copy always)

    
    
    
    
      
      
        
        
        
      
    
      
      
        
      
    
    

提交回复
热议问题