Подтвердить что ты не робот

Настройка Unity для разрешения типа, который принимает оформленную зависимость, которая имеет параметр, который зависит от типа, в который он вводится

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

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

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 требуется другое значение configuration для передачи конструктору его RealThing, скажем, "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) уровне?

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

и тогда это будет "ввести этот IThing в это и что Depender"

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