Механика
На механическом уровне совершенно нормально использовать делегатов, поскольку делегат - это в основном анонимный интерфейс роли .Другими словами, неважно, внедряете ли вы делегат, интерфейс или абстрактный базовый класс.
Концепции
На концептуальном уровне важно помнить цель внедрения зависимостей .Вы можете использовать DI по другим причинам, чем я, но IMO цель DI состоит в том, чтобы улучшить поддерживаемость базы кода.
Независимо от того, достигается ли эта цель путем введения делегатов вместоИнтерфейсы сомнительны.
Делегаты как зависимости
Первая проблема заключается в том, насколько хорошо делегат сообщает намерение .Иногда имя интерфейса сообщает намерение само по себе, тогда как стандартный тип делегата едва ли это делает.
Например, один тип сам по себе не имеет большого смысла здесь:
public BusinessTransactionFactory(Func<Type, IBusinessTransaction> createTranscation)
К счастью, имя createTranscation
все еще подразумевает роль, которую играет делегат, но просто подумайте (ради аргумента), насколько читабельным был бы этот конструктор, если бы автор был менее осторожен:
public BusinessTransactionFactory(Func<Type, IBusinessTransaction> func)
Другими словами, использование делегатов смещает фокус с имени типа на имя аргумента .Это не обязательно проблема - я просто указываю на то, что вам необходимо знать об этом сдвиге.
Обнаруживаемость и сопоставимость
Еще одна проблема связана с обнаруживаемостью и сопоставимостью.когда дело доходит до типов, реализующих зависимости.В качестве примера, оба они реализуют общедоступные 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 на самом деле является симптомомнарушения принципа повторного использования абстракций .Когда вы реорганизуете кодовую базу для повторного использования одной и той же абстракции в нескольких местах, имеет смысл явно смоделировать роль этой абстракции как интерфейс (или абстрактный базовый класс).
Заключение
Интерфейсы - это не просто механика.Они явно моделируют роли в базе кода приложения.Центральные роли должны быть представлены интерфейсами, тогда как одноразовые фабрики и их аналог могут использоваться и реализовываться как делегаты.