Является ли 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 в этом случае. Я прав.
Извините за мой плохой английский