Comment puis-je utiliser le motif décorateur avec Unity sans spécifier explicitement chaque paramètre dans Leconstructeur D'injection

cet article utile de David Haydn (EDIT: scam link removed, it could have been cet article ) montre comment vous pouvez utiliser la classe InjectionConstructor pour vous aider à configurer une chaîne en utilisant le motif décorateur avec unité. Cependant, si les éléments de votre chaîne decorator ont d'autres paramètres dans leur constructeur, le InjectionConstructor doit déclarer explicitement chacun d'eux (ou Unity se plaindra qu'il ne peut pas trouver le bon constructeur). Cela signifie que vous ne pouvez pas il suffit d'ajouter de nouveaux paramètres de constructeur aux éléments de la chaîne decorator sans mettre également à jour votre code de configuration Unity.

voici un exemple de code pour expliquer ce que je veux dire. La classe ProductRepository est enveloppée d'abord par CachingProductRepository et ensuite par LoggingProductRepostiory . Les deux Cachingproductreproductrepository et LoggingProductRepository, en plus de prendre un IProductRepository dans leur constructeur, ont également besoin d'autres interfaces à partir du conteneur.

    public class Product 
    {
        public int Id;
        public string Name;
    }

    public interface IDatabaseConnection { }

    public interface ICacheProvider 
    { 
        object GetFromCache(string key);
        void AddToCache(string key, object value);
    }

    public interface ILogger
    {
        void Log(string message, params object[] args);
    }


    public interface IProductRepository
    {
        Product GetById(int id);    
    }

    class ProductRepository : IProductRepository
    {
        public ProductRepository(IDatabaseConnection db)
        {
        }

        public Product GetById(int id)
        {
            return new Product() { Id = id, Name = "Foo " + id.ToString() };
        }
    }

    class CachingProductRepository : IProductRepository
    {
        IProductRepository repository;
        ICacheProvider cacheProvider;
        public CachingProductRepository(IProductRepository repository, ICacheProvider cp)
        {
            this.repository = repository;
            this.cacheProvider = cp;
        }

        public Product GetById(int id)
        {       
            string key = "Product " + id.ToString();
            Product p = (Product)cacheProvider.GetFromCache(key);
            if (p == null)
            {
                p = repository.GetById(id);
                cacheProvider.AddToCache(key, p);
            }
            return p;
        }
    }

    class LoggingProductRepository : IProductRepository
    {
        private IProductRepository repository;
        private ILogger logger;

        public LoggingProductRepository(IProductRepository repository, ILogger logger)
        {
            this.repository = repository;
            this.logger = logger;
        }

        public Product GetById(int id)
        {
            logger.Log("Requesting product {0}", id);
            return repository.GetById(id);
        }
    }

voici un test d'unité. Voir les commentaires pour les bits de configuration excédentaire que je veux supprimer le besoin pour:

    [Test]
    public void ResolveWithDecorators()
    {
        UnityContainer c = new UnityContainer();            
        c.RegisterInstance<IDatabaseConnection>(new Mock<IDatabaseConnection>().Object);
        c.RegisterInstance<ILogger>(new Mock<ILogger>().Object);
        c.RegisterInstance<ICacheProvider>(new Mock<ICacheProvider>().Object);

        c.RegisterType<IProductRepository, ProductRepository>("ProductRepository");

        // don't want to have to update this line every time the CachingProductRepository constructor gets another parameter
        var dependOnProductRepository = new InjectionConstructor(new ResolvedParameter<IProductRepository>("ProductRepository"), new ResolvedParameter<ICacheProvider>());
        c.RegisterType<IProductRepository, CachingProductRepository>("CachingProductRepository", dependOnProductRepository);

        // don't want to have to update this line every time the LoggingProductRepository constructor changes
        var dependOnCachingProductRepository = new InjectionConstructor(new ResolvedParameter<IProductRepository>("CachingProductRepository"), new ResolvedParameter<ILogger>());
        c.RegisterType<IProductRepository, LoggingProductRepository>(dependOnCachingProductRepository);
        Assert.IsInstanceOf<LoggingProductRepository>(c.Resolve<IProductRepository>());
    }
32
demandé sur BenCr 2011-05-24 15:25:16

6 réponses

une autre approche, grâce à une suggestion de @DarkSquirrel42, est d'utiliser un InjectionFactory . L'inconvénient est que le code doit encore être mis à jour chaque fois qu'un nouveau paramètre de constructeur est ajouté à quelque chose dans la chaîne. Les avantages sont beaucoup plus faciles à comprendre le code, et seulement un enregistrement unique dans le conteneur.

Func<IUnityContainer,object> createChain = container =>
    new LoggingProductRepository(
        new CachingProductRepository(
            container.Resolve<ProductRepository>(), 
            container.Resolve<ICacheProvider>()), 
        container.Resolve<ILogger>());

c.RegisterType<IProductRepository>(new InjectionFactory(createChain));
Assert.IsInstanceOf<LoggingProductRepository>(c.Resolve<IProductRepository>());
25
répondu Mark Heath 2014-06-25 18:16:37

Voir cet article sur la mise en œuvre d'un décorateur extension du conteneur. Cela devrait vous amener là où vous voulez en ce qui concerne le fait de ne pas avoir besoin de modifier votre configuration si vos signatures de constructeur changent.

12
répondu Kenneth Baltrinic 2011-07-28 17:01:41

une autre solution consiste à ajouter des paramètres de type à votre base de code afin d'aider L'unité à résoudre vos types décorés. Heureusement Unity est parfaitement capable de résoudre seuls les paramètres de type et leurs dépendances, nous n'avons donc pas à nous soucier des paramètres du constructeur lors de la définition de la chaîne de décorateur.

L'enregistrement se présenterait comme suit:

unityContainer.RegisterType<IService, Logged<Profiled<Service>>>();

voici un exemple d'implémentation. Remarque le templated décorateurs Logged<TService> et Profiled<TService> . Regardez ci-dessous pour quelques inconvénients que j'ai remarqué jusqu'ici.

public interface IService { void Do(); }

public class Service : IService { public void Do() { } }

public class Logged<TService> : IService where TService : IService
{
    private TService decoratee;
    private ILogger logger;

    public Logged(ILogger logger, TService decoratee) {
        this.decoratee = decoratee;
        this.logger = logger;
    }

    public void Do() {
        logger.Debug("Do()");
        decoratee.Do();
    }
}

public class Profiled<TService> : IService where TService : IService
{
    private TService decoratee;
    private IProfiler profiler;

    public Profiled(IProfiler profiler, TService decoratee) {
        this.decoratee = decoratee;
        this.profiler = profiler;
    }

    public void Do() {
        profiler.Start();
        decoratee.Do();
        profiler.Stop();
    }
}

Inconvénients

  • un enregistrement défectueux comme uC.RegisterType<IService, Logged<IService>>(); résultera en une récursion infinie qui stack-overflows votre application. Cela peut être une vulnérabilité dans une architecture plug-in.
  • ça altère votre base de code dans une certaine mesure. Si jamais tu abandonnes L'unité et que tu changes pour un cadre DI différent, ces paramètres de modèle n'auront plus de sens pour personne.
5
répondu Good Night Nerd Pride 2015-12-02 18:28:49

j'ai créé une méthode d'extension assez rudimentaire pour cela, qui s'est comportée comme prévu quand je l'ai lancé:

public static class UnityExtensions
{
    public static IUnityContainer Decorate<TInterface, TDecorator>(this IUnityContainer container, params InjectionMember[] injectionMembers)
        where TDecorator : class, TInterface
    {
        return Decorate<TInterface, TDecorator>(container, null, injectionMembers);
    }

    public static IUnityContainer Decorate<TInterface, TDecorator>(this IUnityContainer container, LifetimeManager lifetimeManager, params InjectionMember[] injectionMembers)
        where TDecorator : class, TInterface
    {
        string uniqueId = Guid.NewGuid().ToString();
        var existingRegistration = container.Registrations.LastOrDefault(r => r.RegisteredType == typeof(TInterface));
        if(existingRegistration == null)
        {
            throw new ArgumentException("No existing registration found for the type " + typeof(TInterface));
        }
        var existing = existingRegistration.MappedToType;

        //1. Create a wrapper. This is the actual resolution that will be used
        if (lifetimeManager != null)
        {
            container.RegisterType<TInterface, TDecorator>(uniqueId, lifetimeManager, injectionMembers);
        }
        else
        {
            container.RegisterType<TInterface, TDecorator>(uniqueId, injectionMembers);
        }

        //2. Unity comes here to resolve TInterface
        container.RegisterType<TInterface, TDecorator>(new InjectionFactory((c, t, sName) =>
        {
            //3. We get the decorated class instance TBase
            var baseObj = container.Resolve(existing);

            //4. We reference the wrapper TDecorator injecting TBase as TInterface to prevent stack overflow
            return c.Resolve<TDecorator>(uniqueId, new DependencyOverride<TInterface>(baseObj));
        }));

        return container;
    }
}

et dans votre configuration:

container.RegisterType<IProductRepository, ProductRepository>();

// Wrap ProductRepository with CachingProductRepository,
// injecting ProductRepository into CachingProductRepository for
// IProductRepository
container.Decorate<IProductRepository, CachingProductRepository>();

// Wrap CachingProductRepository with LoggingProductRepository,
// injecting CachingProductRepository into LoggingProductRepository for
// IProductRepository
container.Decorate<IProductRepository, LoggingProductRepository>();
4
répondu garryp 2017-07-03 08:42:28

, La plus succincte réponse fonctionne très bien est mentionné dans un autre stackoverflow post par Mark Seeman. Il s'agit de concis , et ne m'oblige pas à utiliser des enregistrements nommés ou à suggérer que j'utilise des extensions D'Unité. Considérons une interface appelée ILogger avec deux implémentations à savoir Log4NetLogger et une implémentation decoratorlogger appelée DecoratorLogger. Vous pouvez enregistrer l' DecoratorLogger contre l'interface ILogger comme suit:

container.RegisterType<ILogger, DecoratorLogger>(
    new InjectionConstructor(
        new ResolvedParameter<Log4NetLogger>()));
3
répondu user3613932 2017-05-23 11:54:38

alors que j'attendais des réponses à ce sujet, j'ai trouvé une solution assez détournée. J'ai créé une méthode d'extension sur IUnityContainer qui me permet d'enregistrer une chaîne de décorateur en utilisant la réflexion pour créer les paramètres Injectionconstructeur:

static class DecoratorUnityExtensions
{
    public static void RegisterDecoratorChain<T>(this IUnityContainer container, Type[] decoratorChain)
    {
        Type parent = null;
        string parentName = null;
        foreach (Type t in decoratorChain)
        {
            string namedInstance = Guid.NewGuid().ToString();
            if (parent == null)
            {
                // top level, just do an ordinary register type                    
                container.RegisterType(typeof(T), t, namedInstance);
            }
            else
            {
                // could be cleverer here. Just take first constructor
                var constructor = t.GetConstructors()[0];
                var resolvedParameters = new List<ResolvedParameter>();
                foreach (var constructorParam in constructor.GetParameters())
                {
                    if (constructorParam.ParameterType == typeof(T))
                    {
                        resolvedParameters.Add(new ResolvedParameter<T>(parentName));
                    }
                    else
                    {
                        resolvedParameters.Add(new ResolvedParameter(constructorParam.ParameterType));
                    }
                }
                if (t == decoratorChain.Last())
                {
                    // not a named instance
                    container.RegisterType(typeof(T), t, new InjectionConstructor(resolvedParameters.ToArray()));
                }
                else
                {
                    container.RegisterType(typeof(T), t, namedInstance, new InjectionConstructor(resolvedParameters.ToArray()));
                }
            }
            parent = t;
            parentName = namedInstance;
        }
    }
}

cela me permet de configurer mon conteneur avec une syntaxe beaucoup plus lisible:

[Test]
public void ResolveWithDecorators2()
{
    UnityContainer c = new UnityContainer();
    c.RegisterInstance<IDatabaseConnection>(new Mock<IDatabaseConnection>().Object);
    c.RegisterInstance<ILogger>(new Mock<ILogger>().Object);
    c.RegisterInstance<ICacheProvider>(new Mock<ICacheProvider>().Object);

    c.RegisterDecoratorChain<IProductRepository>(new Type[] { typeof(ProductRepository), typeof(CachingProductRepository), typeof(LoggingProductRepository) });

    Assert.IsInstanceOf<LoggingProductRepository>(c.Resolve<IProductRepository>());

}

je serais toujours intéressé de savoir s'il y a une solution plus élégante à cela avec L'Unité

0
répondu Mark Heath 2011-05-24 11:51:32