3 Stimmen

Wie lässt sich zyklisches Verhalten mit dem CollectionResolver von Castle Windsor vermeiden?

Ich verwende Castle Windsor 2.5 in meiner Anwendung. Ich habe einen Dienst, der ein Kompositum ist, das als Verteiler für Objekte dient, die dieselbe Schnittstelle implementieren.

public interface IService { void DoStuff(string someArg); }

public class ConcreteService1 : IService {
    public void DoStuff(string someArg) { }
}

public class ConcreteService2 : IService {
    public void DoStuff(string someArg) { }
}

public class CompositeService : List<IService>, IService
{
    private readonly IService[] decoratedServices;

    CompositeService(params IService[] decoratedServices) {
        this.decoratedServices = decoratedServices;
    }

    public void DoStuff(string someArg) {
        foreach (var service in decoratedServices) {
            service.DoStuff(someArg);
        }
    }
}

Das Problem, das ich habe, ist, dass die Verwendung einer Konfiguration wie der unten gezeigten dazu führt, dass Windsor meldet: "Beim Versuch, eine Abhängigkeit aufzulösen, wurde ein Zyklus festgestellt."

windsor.Register(
    Component
        .For<IService>()
        .ImplementedBy<CompositeService>(),

    Component
        .For<IService>()
        .ImplementedBy<ConcreteService1>(),

    Component
        .For<IService>()
        .ImplementedBy<ConcreteService2>()
);

Dieses zyklische Verhalten scheint im Widerspruch zu dem standardmäßigen (nicht sammlungsbasierten) Verhalten zu stehen, das verwendet werden kann, um das Dekoratormuster zu implementieren, bei dem die aufgelöste Komponente ignoriert und stattdessen die nächste registrierte Komponente, die dieselbe Schnittstelle implementiert, verwendet wird.

Ich würde also gerne wissen, ob es eine Möglichkeit gibt, Windsor dazu zu bringen, die CompositeService Komponente, wenn sie die Auflösung der IService Dienstleistungen für die decoratedServices Eigentum. Die decoratedServices Eigenschaft sollte zwei Elemente enthalten: eine Instanz von ConcreteService1 und eine Instanz von ConcreteService2 . Ich möchte dies tun, ohne die Abhängigkeiten explizit anzugeben.

1voto

Aliostad Punkte 78595

Ich kenne Unity, aber ich glaube, Ihr Problem ist ein allgemeines und betrifft alle DI-Systeme. Die meisten DI-Tools haben die Möglichkeit, Ihre Abhängigkeit explizit zu benennen. Dies ist besonders nützlich, wenn Sie die gleiche Schnittstelle implementieren.

Ich glaube also, Ihr Problem ist nicht so sehr die zirkuläre Abhängigkeit (wenn es ein solches Wort gibt), sondern die Vielfältigkeit der Abhängigkeit. Daher würde ich die Abhängigkeiten explizit wie folgt benennen:

windsor.Register( 
Component 
    .For<IService>() 
    .ImplementedBy<CompositeService>("CompositeService"), 

Component 
    .For<IService>() 
    .ImplementedBy<ConcreteService1>("ConcreteService1"), 

Component 
    .For<IService>() 
    .ImplementedBy<ConcreteService2>("ConcreteService2") 

);

Aber ich kann nicht sehen, wie Ihr DI-Framework den Konstruktor Ihres CompositeService behandeln kann. params ist ein schwieriges Thema.

Ich würde folgendermaßen vorgehen:

1) Ich entferne params 2) Ich ändere den Konstruktor in IEnumerable<IService> decoratedServices 3) Ich erstelle eine weitere Klasse und implementiere IEnumerable, die nur eine Fabrik ist, die mir eine Liste von definierten Typen zurückgibt, die ich mit ihren Namen initialisiere.

1voto

Damian Powell Punkte 8397

Hier ist mein derzeitiger Lösungsweg. Ich bin nicht übermäßig vertraut mit den Interna von Windsor, so dass es eklatante Fehler mit dieser Lösung sein könnte, so verwenden Sie es auf eigene Gefahr!

public class NonCyclicCollectionResolver : ISubDependencyResolver
{
    private readonly IKernel kernel;
    private readonly bool    allowEmptyCollections;

    public NonCyclicCollectionResolver(
        IKernel kernel,
        bool    allowEmptyCollections = false
    ) {
        this.kernel                = kernel;
        this.allowEmptyCollections = allowEmptyCollections;
    }

    public bool CanResolve(
        CreationContext        context,
        ISubDependencyResolver contextHandlerResolver,
        ComponentModel         model,
        DependencyModel        dependency
    ) {
        if (dependency.TargetType == null) return false;

        var itemType = dependency.TargetType.GetCompatibileArrayItemType();
        if (itemType == null) return false;

        if (!allowEmptyCollections)
        {
            return GetOtherHandlers(itemType, model.Name).Any();
        }

        return true;
    }

    public object Resolve(
        CreationContext        context,
        ISubDependencyResolver contextHandlerResolver,
        ComponentModel         model,
        DependencyModel        dependency
    ) {
        var itemType = dependency.TargetType.GetCompatibileArrayItemType();

        var handlers = GetOtherHandlers(itemType, model.Name);

        var resolved = handlers
            .Select(h => kernel.Resolve(h.ComponentModel.Name, itemType))
            .ToArray();

        var components = Array.CreateInstance(itemType, resolved.Length);
        resolved.CopyTo(components, 0);
        return components;
    }

    private IEnumerable<IHandler> GetOtherHandlers(
        Type   serviceType,
        string thisComponentName
    ) {
        return kernel
            .GetHandlers(serviceType)
            .Where(h => h.ComponentModel.Name != thisComponentName);
    }
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X