Автоматическая абстрактная фабрика Unity

Я новичок в Unity, и мне трудно понять, как расширить концепцию Unity Auto Factory. Unity предоставляет возможность создавать фабрики с помощью Func вместо самого параметра. Как это:

public class Foo
{
    private readonly Func<IBar> _barFactory;

    public Foo(Func<IBar> barFactory)
    {
        _bar = barFactory;
    }
}

Дело в том, что есть места, где класс, созданный фабрикой, нуждается в некоторых параметрах, и я буду знать эти параметры только во время выполнения. Помимо этого, количество и тип этих параметров варьируется от класса к классу.

Я ищу нечто похожее на Autofac DelegateFacotry, но я хотел бы чтобы сохранить чувство Единства. Итак, я хочу заставить Unity работать так:

public class Foo
{
    private readonly Func<int, string, IBar> _barFactory;

    public Foo(Func<int, string, IBar> barFactory)
    {
        _bar = barFactory;
    }
}

Приведенный выше код не работает, потому что Unity работает с этим конструктором, но это именно то, что я ищу.

Я пытался использовать BuilderStrategy, но это сводится к генерации Expression или IL. Прежде чем пойти по этому пути, я хотел бы проверить другие варианты.

Есть ли у кого-нибудь достаточно опыта работы с Unity, кто может помочь мне с этим?

Вот ограничения, которые у меня есть:

  1. Сохраняйте концепцию Unity по использованию Func
  2. Не нужно регистрировать каждую функцию в контейнере
  3. Конструктор должен принимать Func из Func в Func (Func уже обрабатывается)
  4. Сменить контейнер не вариант

Надеюсь, я был достаточно ясен. Если я не был, пожалуйста, дайте мне знать.

Редактировать 1: я понимаю, что могу напрямую использовать абстрактные фабрики. Но первая цель — сохранить чувство Unity.


person Allan    schedule 30.11.2012    source источник
comment
Это жесткий набор ограничений... Наличие пользовательского делегата позволяет вам сопоставлять имена параметров и, таким образом, использовать переопределения параметров (msdn.microsoft.com/en-us/library/), чтобы создать функцию, которая вызывает обратно контейнер с переопределениями. В вашем примере вы хотите, чтобы Func возвращал интерфейс, что еще больше усложняет решение, поскольку вы можете получить любую из реализаций интерфейсов.   -  person fsimonazzi    schedule 01.12.2012
comment
Я понимаю вашу точку зрения fsimonazzi. Но так было бы проще использовать абстрактную фабрику или зарегистрировать нужные типы. Чего я действительно пытаюсь добиться, так это сохранить способ Unity сделать это без слишком большой настройки. В идеале я бы просто создал вызов контейнера и позволил Unity разобраться с остальным.   -  person Allan    schedule 01.12.2012


Ответы (2)


Я не люблю отвечать на свои вопросы, но мне удалось каким-то образом решить проблему, и я считаю, что решение достаточно хорошее, чтобы другие могли заинтересоваться. Я посмотрел исходный код Unity и получил оттуда основную идею. Я также прочитал пару сообщений о Unity. Вот:

Во-первых, мне пришлось создать класс, унаследованный от IBuildPlanPolicy. Это длинно, потому что я оставил некоторые вспомогательные классы внутри самого класса:

 public class AutomaticFactoryBuilderPolicy : IBuildPlanPolicy
{
    private readonly Dictionary<Type, Type> _callables = 
        new Dictionary<Type, Type>
            {
                {typeof(Func<,>), typeof(CallableType<,>)},
                {typeof(Func<,,>), typeof(CallableType<,,>)},
                {typeof(Func<,,,>), typeof(CallableType<,,,>)},
                {typeof(Func<,,,,>), typeof(CallableType<,,,,>)}
            };

    public void BuildUp(IBuilderContext context)
    {
        if (context.Existing == null)
        {
            var currentContainer = context.NewBuildUp<IUnityContainer>();
            var buildKey = context.BuildKey;

            string nameToBuild = buildKey.Name;

            context.Existing = CreateResolver(currentContainer, buildKey.Type, nameToBuild);
        }
    }

    private Delegate CreateResolver(IUnityContainer currentContainer, 
        Type typeToBuild, string nameToBuild)
    {
        Type[] delegateTypes = typeToBuild.GetGenericArguments();
        Type func = typeToBuild.GetGenericTypeDefinition();
        Type callable = _callables[func];

        Type callableType = callable.MakeGenericType(delegateTypes);
        Type delegateType = func.MakeGenericType(delegateTypes);
        MethodInfo resolveMethod = callableType.GetMethod("Resolve");

        object callableObject = Activator.CreateInstance(callableType, currentContainer, nameToBuild);
        return Delegate.CreateDelegate(delegateType, callableObject, resolveMethod);
    }

    private class CallableType<T1, TResult>
    {
        private readonly IUnityContainer _container;
        private readonly string _name;

        public CallableType(IUnityContainer container, string name)
        {
            _container = container;
            _name = name;
        }

        public TResult Resolve(T1 p1)
        {
            return _container.Resolve<TResult>(_name, new OrderedParametersOverride(new object[] { p1 }));
        }
    }

    private class CallableType<T1, T2, TResult>
    {
        private readonly IUnityContainer _container;
        private readonly string _name;

        public CallableType(IUnityContainer container, string name)
        {
            _container = container;
            _name = name;
        }

        public TResult Resolve(T1 p1, T2 p2)
        {
            return _container.Resolve<TResult>(_name, new OrderedParametersOverride(new object[] { p1, p2 }));
        }
    }

    private class CallableType<T1, T2, T3, TResult>
    {
        private readonly IUnityContainer _container;
        private readonly string _name;

        public CallableType(IUnityContainer container, string name)
        {
            _container = container;
            _name = name;
        }

        public TResult Resolve(T1 p1, T2 p2, T3 p3)
        {
            return _container.Resolve<TResult>(_name, new OrderedParametersOverride(new object[] { p1, p2, p3 }));
        }
    }

    private class CallableType<T1, T2, T3, T4, TResult>
    {
        private readonly IUnityContainer _container;
        private readonly string _name;

        public CallableType(IUnityContainer container, string name)
        {
            _container = container;
            _name = name;
        }

        public TResult Resolve(T1 p1, T2 p2, T3 p3, T4 p4)
        {
            return _container.Resolve<TResult>(_name, new OrderedParametersResolverOverride(new object[] { p1, p2, p3, p4 }));
        }
    } 

}

Это довольно просто. Хитрость заключается в том, чтобы создать один CallableType для каждого Func, который я хочу обработать. Он не такой динамичный, как мне сначала хотелось, но чтобы сделать его более динамичным, я считаю, что мне придется иметь дело либо с IL, либо с деревьями выражений. То, что у меня есть сейчас, меня вполне устраивает.

Во-вторых, Unity обрабатывает параметры по именам, а мне приходилось работать с ними по порядку. Здесь в игру вступает OrderedParametersResolverOverride (этот класс используется в приведенном выше коде. Проверьте CallableType классов):

public class OrderedParametersResolverOverride : ResolverOverride
{
    private readonly Queue<InjectionParameterValue> _parameterValues;

    public OrderedParametersResolverOverride(IEnumerable<object> parameterValues)
    {
        _parameterValues = new Queue<InjectionParameterValue>();
        foreach (var parameterValue in parameterValues)
        {
            _parameterValues.Enqueue(InjectionParameterValue.ToParameter(parameterValue));
        }
    }

    public override IDependencyResolverPolicy GetResolver(IBuilderContext context, Type dependencyType)
    {
        if (_parameterValues.Count < 1)
            return null;

        var value = _parameterValues.Dequeue();
        return value.GetResolverPolicy(dependencyType);
    }
}

Эти два класса имеют дело с Func созданием. Следующим шагом будет добавление этого компоновщика в конвейер Unity. Нам нужно создать UnityContainerExtension:

public class AutomaticFactoryExtension: UnityContainerExtension
{
    protected override void Initialize()
    {
        var automaticFactoryBuilderPolicy = new AutomaticFactoryBuilderPolicy();

        Context.Policies.Set(typeof(Microsoft.Practices.ObjectBuilder2.IBuildPlanPolicy),
            automaticFactoryBuilderPolicy,
            new Microsoft.Practices.ObjectBuilder2.NamedTypeBuildKey(typeof(Func<,>)));

        Context.Policies.Set(typeof(Microsoft.Practices.ObjectBuilder2.IBuildPlanPolicy),
            automaticFactoryBuilderPolicy,
            new Microsoft.Practices.ObjectBuilder2.NamedTypeBuildKey(typeof(Func<,,>)));

        Context.Policies.Set(typeof(Microsoft.Practices.ObjectBuilder2.IBuildPlanPolicy),
            automaticFactoryBuilderPolicy,
            new Microsoft.Practices.ObjectBuilder2.NamedTypeBuildKey(typeof(Func<,,,>)));

        Context.Policies.Set(typeof(Microsoft.Practices.ObjectBuilder2.IBuildPlanPolicy),
            automaticFactoryBuilderPolicy,
            new Microsoft.Practices.ObjectBuilder2.NamedTypeBuildKey(typeof(Func<,,,,>)));
    }
}

Последняя часть — добавить этот класс в конвейер Unity:

IUnityContainer container = new UnityContainer();
container.AddExtension(new AutomaticFactoryExtension());

В остальном регистрация стандартная.

Теперь можно иметь конструкторы от Func<> до Fun<,,,,>. Например, теперь обрабатывается следующий конструктор (при условии, что возможно разрешить IFoo):

public class Bar
{
    private readonly Func<int, string, IFoo> _fooFactory;

    public Bar(Func<int, string, IFoo> fooFactory)
    {
        _fooFactory = fooFactory;
    }
}

Дайте мне знать, если есть какие-либо вопросы.

Надеюсь это поможет.

person Allan    schedule 01.12.2012
comment
Это хорошее решение, но обратите внимание, что для него, очевидно, требуется, чтобы конкретный ctor был выбран единством, которое соответствует сигнатуре Func, и если они не совпадают, вы получите ошибки во время выполнения. Также имейте в виду, что InjectionParameterValue.ToParameter изменит экземпляры Type на запросы для разрешения зависимостей. Поэтому, если вам нужно передать тип в качестве параметра ctor, вам нужно будет обернуть его в InjectionParameter‹Type›, и это должно быть в подписи функции, которую вы разрешаете. - person fsimonazzi; 03.12.2012
comment
Да, я понимаю ограничение конструктора. Но этого достаточно, чтобы решить мою проблему. я не знал об ограничении типа. Мне не нужно использовать тип в качестве аргументов конструктора (пока). В любом случае, у вас есть пример кода (от начала до конца), чтобы справиться с этим? - person Allan; 04.12.2012
comment
Если бы у вас было это: открытый интерфейс IFoo { void DoSomething(); } открытый класс Bar: IFoo { private int theInt; частный тип theType; общественная панель (int theInt, Type theType) { this.theInt = theInt; this.theType = тип; } public void DoSomething() { Console.WriteLine(this.theInt + + this.theType.Name); } } - person fsimonazzi; 05.12.2012
comment
Вы бы разрешили функцию следующим образом: var factory = container.Resolve‹Func‹int, InjectionParameter‹Type›, IFoo››(); var instance = factory(199, новый InjectionParameter‹Type›(typeof(string))); instance.DoSomething(); - person fsimonazzi; 05.12.2012
comment
Аккуратный! Спасибо за помощь, fsimonazzi. - person Allan; 05.12.2012

Думали ли вы о создании делегата, регистрации и экземпляре этого в Unity? Таким образом, вы бы назвали параметры и комментарии к этим параметрам, а также к самому делегату. Тогда вам не нужно было бы создавать политики сборки, и ваш код был бы более читабельным.

person JamesD    schedule 02.07.2014