Означает ли принцип инверсии зависимостей, что я должен создавать интерфейс для каждого модуля?

Если я хочу, чтобы мой код следовал принципам SOLID, в частности принципу инверсии зависимостей, означает ли это, что я должен создавать интерфейс (абстракцию) для каждого модуля, даже если он имеет только одну реализацию?

На мой взгляд, и согласно этим сообщениям:

http://josdejong.com/blog/2015/01/06/code-reuse/

http://blog.ploeh.dk/2010/12/02/Interfacesarenotabstractions/

создание «абстракции» для каждого модуля — это беспорядок в коде и нарушение принципа YAGNI.

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

Может ли кто-нибудь прояснить это для меня? Означает ли SOLID, что я должен внедрять каждый модуль и абстрагировать его? Если да, то разве это не беспорядок, который мы просто не собираемся использовать в большинстве случаев?


person michal.ciurus    schedule 27.02.2015    source источник


Ответы (1)


Принцип инверсии зависимостей гласит:

Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций.

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

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

Однако, если вы обнаружите, что определяете множество абстракций только с одной реализацией, вы нарушаете Принцип повторного использования абстракции, как уже заявил Марк Симанн в статье, на которую вы ссылаетесь:

Наличие только одной реализации данного интерфейса — это запах кода.

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

Вот несколько предложений по функциональности, которую вы можете разместить за одной и той же общей абстракцией:

  • ICommandHandler‹TCommand> для классов, которые вносят изменения в систему от имени пользователя (варианты использования).
  • IQueryHandler‹TQuery, TResult> в качестве абстракции для классы, которые запрашивают базу данных (или файловую систему, веб-службу, что угодно) и возвращают данные.
  • IValidator<T> для классов, которые проверяют сообщения об ошибках проверки обратно пользователю
  • ISecurityValidator<T> для классов, которые проверяют, разрешено ли пользователю выполнять определенную операцию.
  • IAuthorizationFilter<T> для классов, которые позволяют применять безопасность на основе строк на основе разрешений и ролей пользователя.
  • IEventHandler<T> для классов, которые реагируют на определенное бизнес-событие.

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

Приложения, которые я пишу, используют эти общие абстракции, и эти приложения имеют всего несколько интерфейсов с одной реализацией. От 90% до 98% модулей в системе реализуют одну из этих общих абстракций (немного в зависимости от размера приложения; чем больше приложение, тем выше процент).

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

person Steven    schedule 28.02.2015