Я подозреваю, что допустил ошибку школьника и ищу уточнения. Многие классы в моем решении (C #) - осмелюсь сказать, что большинство - я закончил тем, что написал соответствующий интерфейс для. Например, интерфейс «ICalculator» и класс «Calculator», который его реализует, хотя я вряд ли заменю этот калькулятор другой реализацией. Кроме того, большинство этих классов находятся в том же проекте, что и их зависимости - они действительно должны быть internal
, но в конечном итоге стали public
побочным эффектом реализации их соответствующих интерфейсов.
Я думаю, что эта практика создания интерфейсов для всего произошла из нескольких заблуждений:
1) Первоначально я думал, что интерфейс необходим для создания макетов модульного теста (я использую Moq), но с тех пор я обнаружил, что класс может быть смоделирован, если его члены virtual
, и у него есть конструктор без параметров (поправьте меня, если Я не прав).
2) Первоначально я думал, что интерфейс необходим для регистрации класса в IoC Framework (Castle Windsor), например
Container.Register(Component.For<ICalculator>().ImplementedBy<Calculator>()...
когда на самом деле я мог просто зарегистрировать конкретный тип против себя:
Container.Register(Component.For<Calculator>().ImplementedBy<Calculator>()...
3) Использование интерфейсов, например параметров конструктора для внедрения зависимостей, приводит к «слабой связи».
Так я сошел с ума от интерфейсов ?! Мне известны сценарии, в которых вы «обычно» используете интерфейс, например, публикуете публичный API или что-то вроде «подключаемой» функциональности. Мое решение имеет небольшое количество классов, которые подходят для таких вариантов использования, но мне интересно, если все другие интерфейсы не нужны, и должны быть удалены? Что касается пункта 3) выше, не буду ли я нарушать "слабую связь", если я буду делать это?
Изменить : - Я просто играю с Moq, и мне кажется, что методы должны быть открытыми и виртуальными, и иметь открытый конструктор без параметров, чтобы иметь возможность их издеваться. Похоже, у меня не может быть внутренних классов?
источник
Ответы:
В общем, если вы создаете интерфейс, в котором есть только один разработчик, вы просто пишете вещи дважды и теряете время. Одни только интерфейсы не обеспечивают слабой связи, если они тесно связаны с одной реализацией ... Тем не менее, если вы хотите высмеивать эти вещи в модульных тестах, это, как правило, является хорошим признаком того, что вам в действительности неизбежно понадобится более одного разработчика код.
Я бы посчитал это немного запахом кода, если почти все ваши классы имеют интерфейсы. Это означает, что они почти все так или иначе работают друг с другом. Я подозреваю, что классы делают слишком много (так как нет вспомогательных классов) или вы слишком много абстрагируете (о, я хочу интерфейс провайдера для гравитации, так как это может измениться!).
источник
1) Даже если конкретные классы являются поддельными, все равно требуется, чтобы вы
virtual
создавали члены и предоставляли конструктор без параметров, который может быть навязчивым и нежелательным. Вы (или новые члены команды) скоро обнаружите, что систематически добавляетеvirtual
конструкторы и параметры без параметров в каждый новый класс, не задумываясь, просто потому, что «так все и работает».Интерфейс - это IMO, гораздо лучшая идея, когда вам нужно смоделировать зависимость, потому что он позволяет отложить реализацию до того момента, когда он вам действительно понадобится, и обеспечивает хороший четкий контракт зависимости.
3) Как это ложь?
Я считаю, что интерфейсы отлично подходят для определения протоколов обмена сообщениями между взаимодействующими объектами. Могут быть случаи, когда необходимость в них является дискуссионной, как, например, для доменных сущностей . Однако, где бы у вас ни был стабильный партнер (т. Е. Внедренная зависимость, а не временная ссылка), с которой вам нужно общаться, вам следует рассмотреть возможность использования интерфейса.
источник
Идея IoC состоит в том, чтобы сделать конкретные типы заменяемыми. Таким образом, даже если (прямо сейчас) у вас есть только один калькулятор, который реализует ICalculator, вторая реализация может зависеть только от интерфейса, а не от деталей реализации из Calculator.
Таким образом, первая версия вашей регистрации IoC-контейнера является правильной и той, которую вы должны использовать в будущем.
Если вы делаете ваши уроки общедоступными или внутренними, это на самом деле не связано, и ни один из них не является moq-ing.
источник
Я действительно был бы более обеспокоен тем фактом, что вы, похоже, чувствуете необходимость "издеваться" почти над каждым типом всего вашего проекта.
Двойные тесты в основном полезны для изоляции вашего кода от внешнего мира (сторонних библиотек, дискового ввода-вывода, сетевого ввода-вывода и т. Д.) Или от действительно дорогих вычислений. Если вы слишком либеральны с вашей структурой изоляции (вам даже ДЕЙСТВИТЕЛЬНО она нужна? Является ли ваш проект настолько сложным?), То это может легко привести к тестам, связанным с реализацией, и это сделает ваш проект более жестким. Если вы напишете несколько тестов, которые проверяют, что какой-то класс вызвал метод X и Y в этом порядке, а затем передали параметры a, b и c в метод Z, вы ДЕЙСТВИТЕЛЬНО получаете значение? Иногда вы получаете такие тесты при тестировании логирования или взаимодействия со сторонними библиотеками, но это должно быть исключением, а не правилом.
Как только ваша инфраструктура изоляции скажет вам, что вы должны сделать материал общедоступным и что у вас всегда должны быть конструкторы без параметров, пришло время чертовски увернуться, потому что в этот момент ваша инфраструктура вынуждает вас писать плохой (худший) код.
Говоря более конкретно об интерфейсах, я считаю, что они полезны в нескольких ключевых случаях:
Когда вам нужно отправлять вызовы методов разным типам, например, когда у вас есть несколько реализаций (это очевидно, поскольку определение интерфейса в большинстве языков является просто набором виртуальных методов)
Когда вам нужно быть в состоянии изолировать остальную часть вашего кода от некоторого класса. Это нормальный способ абстрагирования файловой системы, доступа к сети, базы данных и т. Д. От основной логики вашего приложения.
Когда вам нужно инвертировать управление для защиты границ приложения. Это один из самых мощных способов управления зависимостями. Мы все знаем, что высокоуровневые модули и низкоуровневые модули не должны знать о каждом другом, потому что они будут меняться по разным причинам, и вы НЕ хотите иметь зависимости от HttpContext в вашей доменной модели или от какой-либо инфраструктуры рендеринга PDF в вашей библиотеке умножения матриц. , Заставить ваши граничные классы реализовывать интерфейсы (даже если они никогда не заменяются) помогает ослабить связь между уровнями и резко снижает риск просачивания зависимостей через слои из типов, знающих слишком много других типов.
источник
Я склоняюсь к идее, что, если какая-то логика является частной, то реализация этой логики не должна никого волновать и поэтому не должна проверяться. Если какая-то логика достаточно сложна, и вы хотите ее протестировать, эта логика, вероятно, играет особую роль и должна быть общедоступной. Например, если я извлекаю некоторый код в частном вспомогательном методе, я нахожу, что это обычно то, что можно с таким же успехом размещать в отдельном общедоступном классе (форматере, парсере, преобразователе и т. Д.).
Что касается интерфейсов, я позволил необходимости заглушить или издеваться над классом. Такие вещи, как репо, я обычно хочу иметь возможность заглушки или издеваться. Картограф в интеграционном тесте (обычно) не так уж и много.
источник