Является ли Func <in T, out TResult> подходящим для использования в качестве ctor arg при применении Injection Dependency?

Пример:

public class BusinessTransactionFactory<T>  where T : IBusinessTransaction
{
    readonly Func<Type, IBusinessTransaction> _createTransaction;

    public BusinessTransactionFactory(Func<Type, IBusinessTransaction> createTransaction)
    {
        _createTransaction = createTransaction;
    }

    public T Create()
    {
        return (T)_createTransaction(typeof(T));
    }
}

Код настройки контейнера, использующий его:

public class DependencyRegistration : Registry
{
    public DependencyRegistration()
    {
        Scan(x =>
        {
            x.AssembliesFromApplicationBaseDirectory();
            x.WithDefaultConventions();
            x.AddAllTypesOf(typeof(Repository<>));
            x.ConnectImplementationsToTypesClosing(typeof(IRepository<>));
        });

        Scan(x =>
        {
            x.AssembliesFromApplicationBaseDirectory();
            x.AddAllTypesOf<IBusinessTransaction>();
            For(typeof(BusinessTransactionFactory<>)).Use(typeof(BusinessTransactionFactory<>));
            For<Func<Type, IBusinessTransaction>>().Use(type => (IBusinessTransaction)ObjectFactory.GetInstance(type));
        });


        For<ObjectContext>().Use(() => new ManagementEntities());
    }
}

Как вы думаете?

Ответ 1

Механика

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

Основные понятия

На концептуальном уровне важно иметь в виду цель Injection Dependency. Вы можете использовать DI по разным причинам, кроме меня, но IMO Цель DI - улучшить ремонтопригодность базы кода.

Является ли эта цель достигнутой путем ввода делегатов вместо интерфейсов, является сомнительной.

Делегаты как зависимости

Первая проблема заключается в том, насколько хорошо делегат сообщает о намерениях. Иногда имя интерфейса передает сам смысл, тогда как стандартный тип делегата почти не работает.

В качестве примера, один тип не связывает много намерений здесь:

public BusinessTransactionFactory(Func<Type, IBusinessTransaction> createTranscation)

К счастью, имя createTranscation все еще подразумевает роль, которую играет делегат, но просто рассмотрите (ради аргумента), насколько читаемым был бы этот конструктор, если бы автор был менее осторожен:

public BusinessTransactionFactory(Func<Type, IBusinessTransaction> func)

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

Обнаружение и композиционная способность

Еще одна проблема заключается в способности к обнаружению и сравнению, когда речь заходит о типах, реализующих зависимости. В качестве примера обе эти функции реализуют public Func<Type, IBusinessTransaction>:

t => new MyBusinessTransaction()

и

public class MyBusinessTransactionFactory
{
    public IBusinessTransaction Create(Type t)
    {
        return new MyBusinessTransaction();
    }
}

Однако, в случае класса, это почти случайно, что конкретный не виртуальный метод Create соответствует желаемому делегату. Это очень сложно, но не очень доступно для обнаружения.

С другой стороны, когда мы используем интерфейсы, классы становятся частью отношений is-a, когда они реализуют интерфейсы, поэтому обычно становится проще находить всех исполнителей и группировать их и составлять соответственно.

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

Интерфейсы 1:1 по сравнению с RAP

Некоторые люди заметили, что при попытке использовать DI они имеют много интерфейсов 1:1 (например, IFoo и соответствующий класс Foo). В этих случаях интерфейс (IFoo) кажется избыточным, и кажется соблазнительным просто избавиться от интерфейса и использовать вместо него делегат.

Однако многие интерфейсы 1:1 действительно являются признаком нарушения принципа Reused Abstractions. Когда вы реорганизуете базу кода для повторного использования одной и той же абстракции в нескольких местах, имеет смысл явно моделировать роль этой абстракции как интерфейса (или абстрактного базового класса).

Заключение

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

Ответ 2

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

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

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

Ответ 3

Мне лично не нравится видеть аргументы Func<T> в качестве зависимостей в моих классах, потому что я думаю, что он делает код менее читаемым, но я знаю, что многие разработчики не согласны со мной. Некоторые контейнеры, такие как Autofac, даже позволяют разрешать делегаты Func<T> (возвращая () => container.Resolve<T>()).

Однако есть два случая, когда я не возражаю вносить делегаты Func<T>:

  • Когда класс, который принимает аргумент конструктора Func<T>, находится в Root Composition, поскольку в этом случае он является частью конфигурации контейнера и я не рассматриваю эту часть дизайна приложения.
  • Когда этот Func<T> вводится в одном типе приложения. Когда больше сервисов запускается в зависимости от того же Func<T>, было бы лучше для читаемости создать специальный интерфейс I[SomeType]Factory и вставить это.

Ответ 4

@Steven ответ очень хорошо продумана; лично я нахожу ограниченным использование аргументов Func<T>.

То, что я не понимаю, это значение BusinessTransactionFactory<T> и IBusinessTransactionFactory<T>. Это всего лишь обертки вокруг делегата. Предположительно, вы вводите IBusinessTransactionFactory<T> в другое место. Почему бы просто не добавить туда делегата?

Я думаю о Func<T>Func<T, TResult> и т.д.) как о фабриках. Для меня у вас есть класс factory, реализующий интерфейс factory, завершающий делегат factory, и это кажется излишним.

Ответ 5

Я думаю, что реальный вопрос заключается в том, следует ли считать тип зависимостей контрактом.

В случае делегата Func вы объявляете зависимость от определенной структуры метода, но не более того. Вы не можете вывести диапазон принятых входных значений (предварительные условия), ограничения на ожидаемый результат (пост-условия) или то, что они подразумевают именно, глядя на эту структуру. Например, имеет значение null приемлемое возвращаемое значение? И если это так, то как это следует интерпретировать?

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

Ответ 6

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

Ответ 7

ЕСЛИ вы вводите Func я тонкий код будет ленивым, так что в некоторых случаях, если ваш класс зависит от множества других зависимостей, возможно, это beter для ввода Funct в этом случае. Я прав.

Извините за мой плохой английский