it-swarm.com.de

Werksmethode mit DI und IoC

Ich bin mit diesen Mustern vertraut, weiß aber noch nicht, wie ich mit der folgenden Situation umgehen soll:

public class CarFactory
{
     public CarFactory(Dep1,Dep2,Dep3,Dep4,Dep5,Dep6)
     {
     }

     public ICar CreateCar(type)
     {
            switch(type)
            {
               case A:
                   return new Car1(Dep1,Dep2,Dep3);
               break;

               case B:
                   return new Car2(Dep4,Dep5,Dep6);
               break;
            }
     }
}

Im Allgemeinen liegt das Problem in der Anzahl der Referenzen, die eingefügt werden müssen. Noch schlimmer wird es, wenn es mehr Autos gibt.

Der erste Ansatz, der mir in den Sinn kommt, ist, Car1 und Car2 in den Fabrikkonstruktor einzuspritzen. Dies ist jedoch gegen den Fabrikansatz, da das Werk immer dasselbe Objekt zurückgibt. Der zweite Ansatz besteht darin, den Servicelocator einzuspritzen, er ist jedoch überall gleich. Wie man es löst?

Bearbeiten:

Alternativer Weg 1:

public class CarFactory
{
     public CarFactory(IContainer container)
     {
        _container = container;
     }

     public ICar CreateCar(type)
     {
            switch(type)
            {
               case A:
                   return _container.Resolve<ICar1>();
               break;

               case B:
                     return _container.Resolve<ICar2>();
               break;
            }
     }
}

Alternativer Weg 2 (zu schwer zu verwenden, weil zu viele Abhängigkeiten im Baum vorhanden sind):

public class CarFactory
{
     public CarFactory()
     {
     }

     public ICar CreateCar(type)
     {
            switch(type)
            {
               case A:
                   return new Car1(new Dep1(),new Dep2(new Dep683(),new Dep684()),....)
               break;

               case B:
                    return new Car2(new Dep4(),new Dep5(new Dep777(),new Dep684()),....)
               break;
            }
     }
}
31
MistyK

Eine Wechselfall-Anweisung innerhalb einer Fabrik zu haben, ist ein Codegeruch. Interessanterweise scheinen Sie sich nicht auf die Lösung dieses Problems zu konzentrieren.

Die beste, DI-freundlichste Lösung für dieses Szenario ist das Strategiemuster . Dadurch kann Ihr DI-Container die Abhängigkeiten in die Factory-Instanzen einfügen, zu denen sie gehören, ohne andere Klassen mit diesen Abhängigkeiten zu stören oder auf einen Service Locator zurückzugreifen.

Schnittstellen

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

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

Fabriken

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

Strategie

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

Verwendungszweck

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

Beachten Sie, dass Sie, da es keine Anweisung zum Wechseln zwischen Groß- und Kleinschreibung gibt, der Strategie weitere Factorys hinzufügen können, ohne das Design zu ändern. Jede dieser Factorys kann ihre eigenen Abhängigkeiten haben, die vom DI-Container eingefügt werden.

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));
59
NightOwl888

Beantworten Sie Ihren Kommentar zum Codebeispiel mit Composition Root. Sie können Folgendes erstellen und dies ist kein 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);
 }

und so sieht Ihr Composition Root mit Unity DI-Container aus:

Func<Type, ICar> carFactoryFunc = type => (ICar)container.Resolve(type);
container.RegisterInstance<CarFactory>(new CarFactory(carFactoryFunc));
5
Arkadiusz K

Ich habe vor einiger Zeit eine ähnliche Frage beantwortet. Grundsätzlich dreht sich alles um Ihre Wahl. Sie müssen zwischen Ausführlichkeit (die Ihnen mehr Hilfe von einem Compiler bietet) und Automatisierung wählen, die es Ihnen ermöglicht, weniger Code zu schreiben, der jedoch anfälliger für Fehler ist.

This ist meine Antwort, die Ausführlichkeit unterstützt.

Und this ist auch eine gute Antwort, die Automatisierung unterstützt.

[~ # ~] bearbeite [~ # ~]

Ich glaube, der Ansatz, den Sie für falsch halten, ist der beste. Um die Wahrheit zu sagen, normalerweise gibt es dort nicht so viele Abhängigkeiten. Ich mag diesen Ansatz, weil er sehr explizit ist und selten zu Laufzeitfehlern führt.

Alternative Weg 1:

Dieser ist schlecht. Es ist eigentlich ein Service-Locator, der als Anti-Pattern gilt.

Alternativer Weg 2

Wie Sie geschrieben haben, ist es nicht einfach zu verwenden, wenn es mit IOC containter gemischt wird. In einigen Fällen kann jedoch ein ähnlicher Ansatz ( DI des armen Mannes ) nützlich sein.

Alles in allem würde ich mir nicht die Mühe machen, "viele" Abhängigkeiten in Ihren Fabriken zu haben. Es ist ein einfacher, deklarativer Code. Das Schreiben dauert Sekunden und erspart Ihnen Stunden mit Laufzeitfehlern.

2
Andrzej Gis

Erstens, Sie haben eine Betonfabrik, ein IoC-Container könnte eine Alternative sein und nicht, um Ihnen dort zu helfen.

Dann müssen Sie die Fabrik einfach umgestalten, um nicht eine vollständige mögliche Parameterliste im Fabrikkonstruktor zu erwarten. Dies ist das Hauptproblem - warum übergeben Sie so viele Parameter, wenn die Factory-Methode sie nicht benötigt?

Ich möchte lieber bestimmte Parameter an die Factory-Methode übergeben

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

Durch das Einkapseln der Parameterliste in einer bestimmten Klasse müssen Sie lediglich angeben, dass der Client genau diese Parameter bereitstellt, die für den Aufruf der Factory-Methode erforderlich sind.

Bearbeiten:

Leider war aus Ihrem Beitrag nicht klar, was diese Dep1, ... und wie Sie sie verwenden.

Ich schlage folgenden Ansatz vor, der den Werksanbieter von der tatsächlichen Werksimplementierung trennt. Dieser Ansatz wird als Local Factory Muster bezeichnet:

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

Die Factory selbst hat keine Implementierung. Hier legen Sie die Basis für Ihre Domain-API fest, wo Ihre Auto-Instanzen nur mit dieser API erstellt werden sollen.

Anschließend konfigurieren Sie im Kompositionsstamm (irgendwo in der Nähe des Startpunkts der App, an dem Sie Ihren tatsächlichen Container konfigurieren) den Anbieter:

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

Beachten Sie, dass diese Beispielimplementierung des Factory-Anbieters einen Delegaten verwendet. Eine Schnittstelle könnte jedoch auch als Spezifikation für einen tatsächlichen Provider verwendet werden.

Diese Implementierung ist im Grunde die Nummer 1 Ihrer bearbeiteten Frage, hat jedoch keine besonderen Nachteile. Der Client ruft noch an:

var car = new CarFactory().CreareCar( type );
1
Wiktor Zychla

Ich würde in Betracht ziehen, den Abhängigkeiten eine gute Struktur zu geben, damit Sie etwas ähnliches verwenden können, das der Antwort von Wiktor ähnelt, aber ich würde die Autofabrik abstrahieren. Dann verwenden Sie die if..then-Struktur nicht.

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

Ich habe es nicht aufgeführt, aber Sie können jetzt mehrere Fahrzeugtypen und die entsprechenden Fabriken implementieren und DI dazu verwenden, um zu injizieren, was Sie benötigen.

1
user4275029

Viele DI-Container unterstützen die Vorstellung benannter Abhängigkeiten.

Z.B. (Structuremap-Syntax)

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

Wenn Sie so etwas wie eine Konvention verwenden, eine Regel, wie der Name vom konkreten Fahrzeugtyp selbst abgeleitet wird, kann es vorkommen, dass Sie beim Erweitern des Systems nicht mehr die Fabrik berühren müssen.

Die Verwendung in einer Fabrik ist unkompliziert.

class Factory(IContainer c) {
  public ICar GetCar(string name) {
    Return c.GetNamedInstance(name);
  }
}
0
flq