Принципы SOLID.
5. Принцип инверсии зависимостей (DIP)
«Модули верхнего уровня не должны зависеть от модулей нижнего уровня. И те и другие должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций» (Мартин Р. Принципы, паттерны и практики гибкой разработки. — 2006).
Принцип инверсии зависимостей лежит в основе популярных техник внедрения зависимостей (Dependency Injection). В основе принципа инверсии зависимостей лежит идея использования интерфейсов. Одна группа классов реализует некоторый набор интерфейсов, а другая — принимает эти интерфейсы в качестве аргументов конструктора.
interface IReader {
string Read();
}
class Report {
public Report(IReader reader) {…}
}
class FileReader : IReader {…}
Использование интерфейсов приводит к слабосвязанному (loosely coupled) дизайну, поскольку класс Report знает лишь об интерфейсе IReader и не знает о конкретной реализации этого интерфейса. А следование принципу LSP позволит заменить одну реализацию другой и получить гибкое решение, соответствующее принципу OCP.Однако не стоит забывать, что наличие интерфейсов образует дополнительный уровень абстракции, что затрудняет понимание системы. Не для всех классов нужно выделять интерфейсы, и не все зависимости следует требовать извне в виде интерфейсов.
Слои
Любое современное приложение разбито на слои, каждый из которых отвечает за определенный аспект поведения: доступа к данным, бизнес-логики, UI и т.п. Каждый слой отвечает за определенную область и использует сервисы нижележащих уровней для решения своих задач. Принцип SRP говорит, что каждый класс, модуль или слой должен решать лишь одну задачу. Это значит, что код доступа к данным не должен содержать бизнес-логики, а бизнес-логика не должна знать о UI. Слои нижнего уровня не знают и не должны знать о слоях верхнего уровня. Это позволяет использовать низкоуровневые слои повторно, упрощает понимание и развитие каждого из них, а также ограничивает распространение изменений.
Для чего нужен DIP
DIP предназначен для устранения прямых связей между классами или модулями с зависимостями более высокого уровня. Название отражает нетипичность направления зависимостей: классы нижнего уровня определяют некоторый контракт, которому должны следовать классы верхнего уровня. Классы верхнего уровня вынуждены выступать в роли адаптеров и подстраиваться под протокол, определенный на уровне ниже.
Примеры нарушения DIP
1. Низкоуровневые классы напрямую общаются с высокоуровневыми классами: модели в MVC знают о UI или код доступа к данным знает о бизнес-логике.
2. Классы принимают слишком низкоуровневые интерфейсы, такие как
IFileStream, что может привести к подрыву инкапсуляции и излишнему увеличению сложности.Принцип DIP не сводится лишь к выделению интерфейсов и передаче их через конструктор. DIP объясняет, для чего нужно это делать. Классы имеют право контролировать свои детали реализации, но некоторые аспекты находятся за пределами их компетенции. Чтобы не завязываться на классы верхнего уровня, класс может объявить некоторый интерфейс и потребовать его экземпляр через аргументы конструктора. Таким образом мы можем инвертировать зависимости и позволить классам нижних уровней взаимодействовать с другими частями системы, ничего конкретного о них не зная.
Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 21.