Ich bin ein genesender IOC-Süchtiger. Ich finde es schwer, die Verwendung von IOC für DI in den meisten Fällen heutzutage zu rechtfertigen. IOC-Container opfern die Kompilierzeitüberprüfung und bieten im Gegenzug angeblich ein "einfaches" Setup, eine komplexe Lebenszeitverwaltung und eine fliegende Entdeckung von Abhängigkeiten zur Laufzeit. Ich finde, dass der Verlust der Kompilierzeitüberprüfung und die daraus resultierende Laufzeitmagie bzw. die Ausnahmen in den allermeisten Fällen den Schnickschnack nicht wert sind. In großen Unternehmensanwendungen kann es dadurch sehr schwierig werden, die Vorgänge zu verfolgen.
Ich kaufe das Zentralisierungsargument nicht, weil Sie statische Einrichtung sehr leicht zentralisieren können, indem Sie eine abstrakte Fabrik für Ihre Anwendung verwenden und die Objekterstellung religiös auf die abstrakte Fabrik aufschieben, d. h. ordnungsgemäßes DI durchführen.
Warum nicht eine statische, magiefreie DI wie diese:
interface IServiceA { }
interface IServiceB { }
class ServiceA : IServiceA { }
class ServiceB : IServiceB { }
class StubServiceA : IServiceA { }
class StubServiceB : IServiceB { }
interface IRoot { IMiddle Middle { get; set; } }
interface IMiddle { ILeaf Leaf { get; set; } }
interface ILeaf { }
class Root : IRoot
{
public IMiddle Middle { get; set; }
public Root(IMiddle middle)
{
Middle = middle;
}
}
class Middle : IMiddle
{
public ILeaf Leaf { get; set; }
public Middle(ILeaf leaf)
{
Leaf = leaf;
}
}
class Leaf : ILeaf
{
IServiceA ServiceA { get; set; }
IServiceB ServiceB { get; set; }
public Leaf(IServiceA serviceA, IServiceB serviceB)
{
ServiceA = serviceA;
ServiceB = serviceB;
}
}
interface IApplicationFactory
{
IRoot CreateRoot();
}
abstract class ApplicationAbstractFactory : IApplicationFactory
{
protected abstract IServiceA ServiceA { get; }
protected abstract IServiceB ServiceB { get; }
protected IMiddle CreateMiddle()
{
return new Middle(CreateLeaf());
}
protected ILeaf CreateLeaf()
{
return new Leaf(ServiceA,ServiceB);
}
public IRoot CreateRoot()
{
return new Root(CreateMiddle());
}
}
class ProductionApplication : ApplicationAbstractFactory
{
protected override IServiceA ServiceA
{
get { return new ServiceA(); }
}
protected override IServiceB ServiceB
{
get { return new ServiceB(); }
}
}
class FunctionalTestsApplication : ApplicationAbstractFactory
{
protected override IServiceA ServiceA
{
get { return new StubServiceA(); }
}
protected override IServiceB ServiceB
{
get { return new StubServiceB(); }
}
}
namespace ConsoleApplication5
{
class Program
{
static void Main(string[] args)
{
var factory = new ProductionApplication();
var root = factory.CreateRoot();
}
}
//[TestFixture]
class FunctionalTests
{
//[Test]
public void Test()
{
var factory = new FunctionalTestsApplication();
var root = factory.CreateRoot();
}
}
}
Ihre Container-Konfiguration ist Ihre abstrakte Fabrik-Implementierung, Ihre Registrierungen sind Implementierungen von abstrakten Mitgliedern. Wenn Sie eine neue Singleton-Abhängigkeit benötigen, fügen Sie einfach eine weitere abstrakte Eigenschaft zur abstrakten Fabrik hinzu. Wenn Sie eine transiente Abhängigkeit benötigen, fügen Sie einfach eine weitere Methode hinzu und injizieren sie als Func<>.
Vorteile:
- Die gesamte Konfiguration der Einrichtung und Objekterstellung ist zentralisiert.
- Konfiguration ist nur Code
- Die Kompilierzeitprüfung macht die Wartung einfach, da Sie nicht vergessen können, Ihre Registrierungen zu aktualisieren.
- Keine Reflexionsmagie während der Laufzeit
Skeptikern empfehle ich, es beim nächsten Projekt auf der grünen Wiese zu versuchen und sich ehrlich zu fragen, an welcher Stelle man den Container braucht. Es ist einfach, einen IOC-Container zu einem späteren Zeitpunkt einzubauen, da Sie lediglich eine Fabrikimplementierung durch ein IOC-Container-Konfigurationsmodul ersetzen.