Настройка Unity для разрешения типа, который принимает декорированную зависимость, у которой есть параметр, который зависит от типа, в который он внедрен - PullRequest
12 голосов
/ 25 января 2012

Это довольно простой сценарий шаблона декоратора, с усложнением, что декорированный тип имеет параметр конструктора, который зависит от типа, в который он внедряется.

У меня есть такой интерфейс:

interface IThing
{
    void Do();
}

И реализация, подобная этой:

class RealThing : IThing
{
    public RealThing(string configuration)
    {
        ... implementation ...
    }

    public void Do()
    {
        ... implementation ...
    }
}

И декоратор, как это:

class DecoratingThing : IThing
{
    IThing _innerThing;

    public DecoratingThing(IThing thing)
    {
        _innerThing = thing;    
    }

    public void Do()
    {
        _innerThing.Do();
    }
}

Наконец, у меня есть некоторые типы, которые требуют IThing, называется Depender1, Depender2 и т. д.

class DependerX()
{
    public DependerX(IThing thing)
    {
        ... implementation ...
    }
}

Я хочу настроить контейнер IOC для разрешения экземпляров DependerX таким образом, чтобы они вводились с помощью RealThing, украшенным DecoratingThing. Важно: Каждый DependerX тип требует, чтобы в конструктор его RealThing передавалось различное значение configuration, скажем «ConfigX» в каждом случае.Например, работа, выполняемая контейнером IoC, может быть такой:

new Depender1(new DecoratingThing(new RealThing("Config1")));
new Depender2(new DecoratingThing(new RealThing("Config2")));

... и т. д.

В Unity это выглядит довольно неуклюже для настройки, так как мне приходится смешивать в декораторес декорированным:

container.RegisterType<IThing, DecoratingThing>("ConfigX",
    new InjectionFactory(container => new DecoratingThing(new RealThing("ConfigX"));

container.RegisterType<DependerX>(
    new InjectionConstructor(new ResolvedParameter<IThing>("ConfigX");

И повторяю, красиво нарушая DRY, для каждого DependerX.

Что я хотел бы сделать, так это убрать необходимость встраивания конструкции RealThing в конструкции DecoratingThing в каждой именованной регистрации IThing - и объявлять украшение только один раз.Это так, например, что если в будущем необходимо изменить оформление, его проще перенастроить.Лучшее, что я придумал, - это вспомогательный метод для регистрации:

void RegisterDepender<TDepender>(IUnityContainer container, string config)
{
    container.RegisterType<TDepender>(new InjectionConstructor(
        new ResolvedParameter<IThing>(config)));
    container.RegisterType<IThing, DecoratingThing>(config,
        new InjectionFactory(c => new DecoratingThing(new RealThing(config))));
}

Это, по крайней мере, удаляет повторение, но мне все еще нужно встроить конструкцию RealThing в DecoratingThing - это означает, что янапример, не могут изменить время жизни независимо друг от друга.Я не могу зарегистрироваться IThing снова, чтобы сделать это, потому что я использовал мою регистрацию этого интерфейса для имени.Если я хочу сделать это, я должен представить другой набор именованных экземпляров, например, так:

void RegisterDepender<TDepender>(IUnityContainer container, string config)
{
    string realConfig = "Real" + config;

    container.RegisterType<TDepender>(new InjectionConstructor(
        new ResolvedParameter<IThing>(config)));
    container.RegisterType<IThing, DecoratingThing>(config,
        new InjectionFactory(c => new DecoratingThing(
            container.Resolve<IThing>(realConfig))));
    container.RegisterType<IThing, RealThing>(realConfig,
        new ContainerControlledLifetimeManager(),
        new InjectionConstructor(config));
}

Является ли этот действительно лучшим вариантом?Это чувствует себя сложным и потенциально трудным для тех, кто придет после укуса.У других контейнеров IoC есть убедительный способ покрыть этот сценарий?Поскольку шаблон для того, как работает внедрение, повторяется для каждого DependerX, есть ли способ использовать только именованный экземпляр на верхнем (DependerX) уровне?

Любые другие комментарии?

Ответы [ 2 ]

5 голосов
/ 26 января 2012

Сам дизайн класса кажется разумным.Вот основанная на соглашении конфигурация контейнера , которая в основном делает это:

public class MyConventions : UnityContainerExtension
{
    protected override void Initialize()
    {
        var dependers = from t in typeof(IThing).Assembly.GetExportedTypes()
                        where t.Name.StartsWith("Depender")
                        select t;

        foreach (var t in dependers)
        {
            var number = t.Name.TrimStart("Depender".ToArray());
            var realName = "Real" + number;
            var decoName = "Deco" + number;
            var config = "Config" + number;
            this.Container.RegisterType<IThing, RealThing>(realName, 
                new InjectionConstructor(config));
            this.Container.RegisterType<IThing, DecoratingThing>(decoName,
                new InjectionConstructor(
                    new ResolvedParameter<IThing>(realName)));
            this.Container.RegisterType(t,
                new InjectionConstructor(
                    new ResolvedParameter<IThing>(decoName)));
        }
    }
}

Эта конфигурация автоматически добавит все классы, которые соответствуют вышеупомянутому предикату, поэтому, как только вы настроите его, выможно просто добавить дополнительные классы (например, Depender4 или Depender5), не пересматривая конфигурацию контейнера вообще.

Приведенная выше конфигурация удовлетворяет следующим тестам:

[Fact]
public void ContainerCorrectlyResolvesDepender1()
{
    var container = new UnityContainer().AddNewExtension<MyConventions>();
    var actual = container.Resolve<Depender1>();

    var deco = Assert.IsAssignableFrom<DecoratingThing>(actual.Thing);
    var thing = Assert.IsAssignableFrom<RealThing>(deco.Thing);
    Assert.Equal("Config1", thing.Configuration);
}

[Fact]
public void ContainerCorrectlyResolvesDepender2()
{
    var container = new UnityContainer().AddNewExtension<MyConventions>();
    var actual = container.Resolve<Depender2>();

    var deco = Assert.IsAssignableFrom<DecoratingThing>(actual.Thing);
    var thing = Assert.IsAssignableFrom<RealThing>(deco.Thing);
    Assert.Equal("Config2", thing.Configuration);
}

[Fact]
public void ContainerCorrectlyResolvesDepender3()
{
    var container = new UnityContainer().AddNewExtension<MyConventions>();
    var actual = container.Resolve<Depender3>();

    var deco = Assert.IsAssignableFrom<DecoratingThing>(actual.Thing);
    var thing = Assert.IsAssignableFrom<RealThing>(deco.Thing);
    Assert.Equal("Config3", thing.Configuration);
}
3 голосов
/ 25 января 2012

Вы когда-нибудь задумывались о том, чтобы использовать декораторы в Unity Interception? Тогда было бы действительно легко сказать «перехватить вызовы на IThing с помощью этого перехватчика» только один раз.

container.AddNewExtension<Interception>();
container.RegisterType<IThing>(new Interceptor<InterfaceInterceptor>(), new InterceptionBehavior<DecoratingThingBehavior>());

и тогда это будет "впрыснуть это IThing в то и это Depender"

container.RegisterType<Depender1>(new InjectionConstructor(new ResolvedParameter<IThing>("myNameForThing")));
...