Есть ли простой способ зарегистрировать статические замыкания с помощью Castle Windsor? - PullRequest
6 голосов
/ 20 августа 2011

Я экспериментировал с использованием именованных делегатов вместо интерфейсов с одним методом. Это имеет некоторые преимущества для размера кода, как мы можем сделать (некоторые разрывы строк удалены, чтобы не преувеличивать регистр):

public interface IProductSource
{
    IEnumerable<Product> GetProducts();
}
public class DataContextProductSource : IProductSource
{
    private readonly DataContext _DataContext;
    public DataContextProductSource(DataContext dataContext)
    {
        if (dataContext == null) throw new ArgumentNullException("dataContext");
        _DataContext = dataContext;
    }
    public IEnumerable<Product> GetProducts()
    {
        return _DataContext.Products.AsEnumerable();
    }
}

до:

public delegate IEnumerable<Product> DGetProducts();
public static class DataContextFunctions
{
    public DGetProducts GetProducts(DataContext dataContext)
    {
        if (dataContext == null) throw new ArgumentNullException("dataContext");
        return () => dataContext.Products.AsEnumerable();
    }
}

Это в основном использует тот факт, что, как только вы продвинетесь достаточно далеко с внедрением зависимостей, многие классы станут немногим больше, чем замыкания. Эти классы могут быть заменены функциями, которые возвращают лямбда-выражения. Целые наборы связанных функций (которые не должны инкапсулировать какое-либо изменяемое состояние, но были бы выражены с использованием классов в «стандартной» инъекции зависимостей), затем могут быть свернуты в статический класс (или «модуль» на языке VB) .

Это все хорошо, но у меня проблемы с поиском лучшего способа регистрации этих статических методов в Castle Windsor. Методы без каких-либо зависимостей просты:

Component.For<DGetIntegers>().Instance(Integers.GetOneToTen)

Но наши DataContextFunctions.GetProducts выше имеют некоторые зависимости. Лучший способ, который я нашел, чтобы зарегистрировать это:

Component.For<DGetProducts>().UsingFactoryMethod(
    kernel => DataContextFunctions.GetProducts(kernel.Resolve<DataContext>())

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

Вопрос в том, есть ли у Castle Windsor (или любого другого контейнера) простой способ сделать это, что я пропустил, или есть технические проблемы, которые возникают, или это просто слишком нишевый вариант использования для включения

Ответы [ 3 ]

10 голосов
/ 20 августа 2011

Краткий ответ

Вы пытаетесь заставить DI Контейнер (Castle Windsor) выполнять функцию композицию, но на самом деле он нацелен на объект композицию. Это просто даст вам много трений. Я предполагаю, что вы получите такой же опыт с другими контейнерами.

Контейнеры DI разработаны на основе объектно-ориентированных концепций, в частности, SOLID. Они очень хорошо работают с этими принципами, потому что они были разработаны с присущим пониманием таких вещей, как инжектор конструктора и автоматическое подключение.

В более функциональном подходе нет ничего плохого, но мне еще предстоит увидеть DI-контейнер, построенный вокруг композиции функций, а не композиции объектов.

Длинный ответ

Использование делегатов в качестве общего принципа для DI, как правило, проблематично в статически типизированных языках (по крайней мере, в .NET) по нескольким причинам. Концептуально в этом подходе нет ничего плохого, поскольку делегат можно рассматривать как анонимный интерфейс роли . Тем не менее, он становится громоздким из-за неоднозначности типов.

Наиболее распространенный подход, который я обычно вижу, - это использование встроенных делегатов BCL, таких как Func<T>, Action<T> и так далее. Тем не менее, у вас может быть много разных потребителей, которые полагаются на Func<string>, и в этом случае вещи становятся неоднозначными - просто потому, что потребитель требует Func<string>, не означает, что он требует делегата в той же роли. Хотя механически возможно использовать DI с делегатами, получается, что делегаты скрывают роли приложения . Роли исчезают, оставляя только механику.

Затем вы можете, как предложено в OP, определить пользовательские делегаты для каждой роли, как предложено этим делегатом:

public delegate IEnumerable<Product> DGetProducts();

Однако, если вы выберете этот путь, то ничего не получите. «Делегат роли» должен быть определен для каждой роли. Сравните это с определением подобного интерфейса, и должно быть ясно, что единственное сохранение - это пара угловых скобок и явное определение метода:

public interface IProductSource { IEnumerable<Product> GetProducts(); }

Это не много накладных расходов (если есть).

Вы также можете посмотреть это обсуждение: http://thorstenlorenz.wordpress.com/2011/07/23/dependency-injection-is-dead-long-live-verbs/

4 голосов
/ 20 августа 2011

Это интересный подход.Я думаю, что вы могли бы сделать это довольно легко с помощью специального активатора.

1 голос
/ 20 августа 2011

Основываясь на указателях Кшиштофа, я смог написать собственный активатор, чтобы справиться с этим. Источник на github .

Зарегистрируйте статические делегаты следующим образом (следуя примеру в вопросе):

container.Register(Component.
    For<DGetProducts>().
    ImplementedBy(typeof(DataContextFunctions)).
    Named("GetProducts").
    Activator<StaticDelegateActivator>());

Код в значительной степени переписан на DefaultComponentActivator. Мне также нужно было скопировать-вставить DependencyTrackingScope из исходного кода Виндзора, поскольку он внутренний.

Один «тест» в проекте «Тесты» охватывает только один вариант использования. Я не подозреваю, что это будет работать для более сложных сценариев, таких как прокси.

Я принял ответ Кшиштофа, так как его руководство привело к решению.

...