Factory method with DI and IoC

后端 未结 6 623
被撕碎了的回忆
被撕碎了的回忆 2020-11-22 15:30

I am familiar with these patterns but still don\'t know how to handle following situation:

public class CarFactory
{
     public CarFactory(Dep1,Dep2,Dep3,De         


        
相关标签:
6条回答
  • 2020-11-22 15:38

    I answered a similar question some time ago. Basically it's all about your choice. You have to choose between verbosity (which gives you more help from a compiler) and automation, which allows you to write less code but is more prone to bugs.

    This is my answer supporting verbosity.

    And this is also a good answer that supports automation.

    EDIT

    I believe the approach you consider wrong is actually the best. Truth being said, usually there won't so many dependencies in there. I like this approach because it's very explicit and rarely results in runtime errors.

    Alternative way 1:

    This one is bad. It's actually a service locator, which is considered an anti-pattern.

    Alternative way 2

    Like you wrote, it's not easy to use if mixed with IOC containter. However in some case a similar approach (poor man's DI) can be useful.

    All in all, I wouldn't bother having "many" dependencies in your factories. It's a simple, declarative code. It takes seconds to write and can save you hours of struggling with runtime errors.

    0 讨论(0)
  • 2020-11-22 15:41

    Answering your comment about code example with Composition Root. You can create following and this is not a Service Locator.

    public class CarFactory
    {
        private readonly Func<Type, ICar> carFactory;
    
        public CarFactory(Func<Type, ICar> carFactory)
        {
           this.carFactory = carFactory;
        }
    
        public ICar CreateCar(Type carType)
        {
            return carFactory(carType);
     }
    

    and this is how look your Composition Root using Unity DI container :

    Func<Type, ICar> carFactoryFunc = type => (ICar)container.Resolve(type);
    container.RegisterInstance<CarFactory>(new CarFactory(carFactoryFunc));
    
    0 讨论(0)
  • 2020-11-22 15:43

    First, you have a concrete factory, an IoC container could be an alternative rather than something to help you there.

    Then, just refactor the factory to not to expect a full possible parameter list in the factory constructor. This is the primary issue - why are you passing so many parameters if the factory method doesn't need them?

    I would rather pass specific parameters to the factory method

    public abstract class CarFactoryParams { }
    
    public class Car1FactoryParams : CarFactoryParams
    {
       public Car1FactoryParams(Dep1, Dep2, Dep3) 
       { 
          this.Dep1 = Dep1;
          ...
    }
    
    public class Car2FactoryParams 
          ...
    
    public class CarFactory
    {
        public ICar CreateCar( CarFactoryParams params )
        {
            if ( params is Car1FactoryParams )
            {
                var cp = (Car1FactoryParams)params;
                return new Car1( cp.Dep1, cp.Dep2, ... );
            }
            ...
            if ( params is ...
    

    By encapsulating the parameter list in a specific class you just make the client provide exactly these parameters that are required for specific factory method invocation.

    Edit:

    Unfortunately, it was not clear from your post what are these Dep1, ... and how you use them.

    I suggest following approach then that separates the factory provider from actual factory implementation. This approach is known as the Local Factory pattern:

    public class CarFactory
    {
       private static Func<type, ICar> _provider;
    
       public static void SetProvider( Func<type, ICar> provider )
       {
         _provider = provider;
       }
    
       public ICar CreateCar(type)
       {
         return _provider( type );
       }
    }
    

    The factory itself doesn't have any implementation, it is here to set the foundation to your domain API, where you want your car instances to be created with this API only.

    Then, in the Composition Root (somewhere near the starting point of the app where you configure your actual container), you configure the provider:

    CarFactory.SetProvider(
        type =>
        {
            switch ( type )
            {
               case A:
                 return _container.Resolve<ICar1>();
               case B:
                 return _container.Resolve<ICar2>();
               ..
        }
    );
    

    Note that this example implementation of the factory's provider uses a delegate but an interface could also be used as a specification for an actual provider.

    This implementation is basically #1 from your edited question, however, it doesn't have any particular downsides. The client still calls:

    var car = new CarFactory().CreareCar( type );
    
    0 讨论(0)
  • 2020-11-22 15:45

    Having a switch case statement inside of a factory is a code smell. Interestingly, you don't seem to be focusing on solving that issue at all.

    The best, most DI friendly solution for this scenario is the strategy pattern. It allows your DI container to inject the dependencies into the factory instances where they belong, without cluttering up other classes with those dependencies or resorting to a service locator.

    Interfaces

    public interface ICarFactory
    {
        ICar CreateCar();
        bool AppliesTo(Type type);
    }
    
    public interface ICarStrategy
    {
        ICar CreateCar(Type type);
    }
    

    Factories

    public class Car1Factory : ICarFactory
    {
        private readonly IDep1 dep1;
        private readonly IDep2 dep2;
        private readonly IDep3 dep3;
    
        public Car1Factory(IDep1 dep1, IDep2 dep2, IDep3 dep3)
        {
            if (dep1 == null)
                throw new ArgumentNullException("dep1");
            if (dep2 == null)
                throw new ArgumentNullException("dep2");
            if (dep3 == null)
                throw new ArgumentNullException("dep3");
    
            this.dep1 = dep1;
            this.dep2 = dep2;
            this.dep3 = dep3;
        }
    
        public ICar CreateCar()
        {
            return new Car1(this.dep1, this.dep2, this.dep3);
        }
    
        public bool AppliesTo(Type type)
        {
            return typeof(Car1).Equals(type);
        }
    }
    
    public class Car2Factory : ICarFactory
    {
        private readonly IDep4 dep4;
        private readonly IDep5 dep5;
        private readonly IDep6 dep6;
    
        public Car1Factory(IDep4 dep4, IDep5 dep5, IDep6 dep6)
        {
            if (dep4 == null)
                throw new ArgumentNullException("dep4");
            if (dep5 == null)
                throw new ArgumentNullException("dep5");
            if (dep6 == null)
                throw new ArgumentNullException("dep6");
    
            this.dep4 = dep4;
            this.dep5 = dep5;
            this.dep6 = dep6;
        }
    
        public ICar CreateCar()
        {
            return new Car2(this.dep4, this.dep5, this.dep6);
        }
    
        public bool AppliesTo(Type type)
        {
            return typeof(Car2).Equals(type);
        }
    }
    

    Strategy

    public class CarStrategy : ICarStrategy
    {
        private readonly ICarFactory[] carFactories;
    
        public CarStrategy(ICarFactory[] carFactories)
        {
            if (carFactories == null)
                throw new ArgumentNullException("carFactories");
    
            this.carFactories = carFactories;
        }
    
        public ICar CreateCar(Type type)
        {
            var carFactory = this.carFactories
                .FirstOrDefault(factory => factory.AppliesTo(type));
    
            if (carFactory == null)
            {
                throw new Exception("type not registered");
            }
    
            return carFactory.CreateCar();
        }
    }
    

    Usage

    // I am showing this in code, but you would normally 
    // do this with your DI container in your composition 
    // root, and the instance would be created by injecting 
    // it somewhere.
    var strategy = new CarStrategy(new ICarFactory[] {
        new Car1Factory(dep1, dep2, dep3),
        new Car2Factory(dep4, dep5, dep6)
        });
    
    // And then once it is injected, you would simply do this.
    // Note that you could use a magic string or some other 
    // data type as the parameter if you prefer.
    var car1 = strategy.CreateCar(typeof(Car1));
    var car2 = strategy.CreateCar(typeof(Car2));
    

    Note that because there is no switch case statement, you can add additional factories to the strategy without changing the design, and each of those factories can have their own dependencies that are injected by the DI container.

    var strategy = new CarStrategy(new ICarFactory[] {
        new Car1Factory(dep1, dep2, dep3),
        new Car2Factory(dep4, dep5, dep6),
        new Car3Factory(dep7, dep8, dep9)
        });
    
    var car1 = strategy.CreateCar(typeof(Car1));
    var car2 = strategy.CreateCar(typeof(Car2));
    var car3 = strategy.CreateCar(typeof(Car3));
    
    0 讨论(0)
  • 2020-11-22 15:46

    I would consider giving the dependencies a good structure so you can utilize something similar to Wiktor's answer, but I would abstract the Car factory itself. Then, you don't use the if..then structure.

    public interface ICar
    {
        string Make { get; set; }
        string ModelNumber { get; set; }
        IBody Body { get; set; }
        //IEngine Engine { get; set; }
        //More aspects...etc.
    }
    
    public interface IBody
    {
        //IDoor DoorA { get; set; }
        //IDoor DoorB { get; set; }
        //etc
    }
    
    //Group the various specs
    public interface IBodySpecs
    {
        //int NumberOfDoors { get; set; }
        //int NumberOfWindows { get; set; }
        //string Color { get; set; }
    }
    
    public interface ICarSpecs
    {
        IBodySpecs BodySpecs { get; set; }
        //IEngineSpecs EngineSpecs { get; set; }
        //etc.
    }
    
    public interface ICarFactory<TCar, TCarSpecs>
        where TCar : ICar
        where TCarSpecs : ICarSpecs
    {
        //Async cause everything non-trivial should be IMHO!
        Task<TCar> CreateCar(TCarSpecs carSpecs);
    
        //Instead of having dependencies ctor-injected or method-injected
        //Now, you aren't dealing with complex overloads
        IService1 Service1 { get; set; }
        IBuilder1 Builder1 { get; set; }
    }
    
    public class BaseCar : ICar
    {
        public string Make { get; set; }
        public string ModelNumber { get; set; }
        public IBody Body { get; set; }
        //public IEngine Engine { get; set; }
    }
    
    public class Van : BaseCar
    {
        public string VanStyle { get; set; } 
        //etc.
    }
    
    public interface IVanSpecs : ICarSpecs
    {
        string VanStyle { get; set; }
    }
    
    public class VanFactory : ICarFactory<Van, IVanSpecs>
    {
        //Since you are talking of such a huge number of dependencies,
        //it may behoove you to properly categorize if they are car or 
        //car factory dependencies
        //These are injected in the factory itself
        public IBuilder1 Builder1 { get; set; }
        public IService1 Service1 { get; set; }
    
        public async Task<Van> CreateCar(IVanSpecs carSpecs)
        {
            var van = new Van()
            {
               //create the actual implementation here.
            };
            //await something or other
            return van;
        }
    }
    

    I didn't list it, but you can implement multiple types of cars and their corresponding factories now and use DI to inject whatever you need.

    0 讨论(0)
  • 2020-11-22 15:49

    Many DI containers support the notion of named dependencies.

    E.g. (Structuremap syntax)

    For<ICar>().Use<CarA>().Named("aCar");
    Container.GetNamedInstance("aCar") // gives you a CarA instance
    

    If you use something like a convention, a rule how the name is derived from the concrete car type itself, you have a situation where you don't need to touch the factory anymore when you extend the system.

    Using this in a factory is straightforward.

    class Factory(IContainer c) {
      public ICar GetCar(string name) {
        Return c.GetNamedInstance(name);
      }
    }
    
    0 讨论(0)
提交回复
热议问题