В чем разница между открытым / закрытым принципом и принципом инверсии зависимости?
DIP заявляет:
- Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций.
- Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
И OCP заявляет:
Программные объекты (классы, модули, функции и т. Д.) Должны быть открыты для расширения, но закрыты для модификации.
Я думаю, что если мы удовлетворим DIP, он также охватит OCP. Итак, почему мы разделяем эти два принципа?
5 ответов
Дядя Боб Мартин, который популяризировал принципы открытого и закрытого типа (OCP) и принципы обращения зависимостей (DIP) как два из принципов SOLID, утверждает, что DIP возникает в результате применения OCP и принципа замены Лискова:
В этой колонке мы обсуждаем структурные последствия OCP и LSP. Структура, которая вытекает из строгого использования этих принципов, может быть обобщена в принципе сама по себе. Я называю это "Принцип инверсии зависимости" (DIP).
Роберт К. Мартин, Техническая записка, Отчет C++, 1996.
Таким образом, вы правы, утверждая, что каждый экземпляр DIP будет экземпляром OCP, но OCP гораздо более общий. Вот пример использования OCP, но не DIP, с которым я недавно столкнулся. Многие веб-фреймворки имеют понятие сигналов, когда при одном действии сигнал запускается. Объект, отправляющий сигнал, совершенно не знает слушателей, которые зарегистрированы с сигналом. Каждый раз, когда вы хотите добавить больше слушателей к сигналу, вы можете сделать это без изменения отправителя.
Это явно иллюстрирует OCP ("закрыто для модификации, открыто для расширения"), но не DIP, поскольку отправитель не зависит ни от чего, поэтому нет смысла говорить о том, зависит ли это от чего-то более абстрактного или менее.
В более общем смысле вы можете сказать, что Шаблон наблюдателя (один из шаблонов GoF) описывает, как соблюдать OCP, но не DIP. Было бы интересно просмотреть книгу GoF и посмотреть, какие из них имеют отношение к OCP и сколько из них не связано с DIP.
Я думаю, что следование принципу DIP облегчает соблюдение принципа OCP. Однако одно не гарантирует другого.
Например, я могу создать класс, который имеет метод, который принимает параметр Base
, Если base
является абстрактным классом, то я придерживаюсь DIP, поскольку я инвертировал зависимость от вызывающей стороны. Однако, если код в этом методе делает что-то вроде:
if (base is derived)
(derived)base.DoSomethingSpecificToDerived;
elsif (base is evenMoreDerived)
(evenMoreDerived)base.DoSomethingSpecificToEvenMoreDerived;
Тогда он не соответствует OCP, так как мне приходится изменять его каждый раз, когда я добавляю новый производный.
Это очень надуманный пример, но вы поняли мою точку зрения.
DIP расскажет вам, как организовать зависимости. Он не говорит вам, когда вы закончите с определенным интерфейсом.
Грубо говоря, идея OCP - иметь полные, но минималистичные интерфейсы. Другими словами, он говорит вам, когда вы закончили с интерфейсом, но не говорит вам, как этого добиться.
В некотором смысле DIP и OCP являются ортогональными.
Итак, почему мы разделяем эти два принципа?
Что касается шаблонов проектирования и названных принципов, то почти все они имеют следующее:
Найдите то, что меняется, и заключите в капсулу
Предпочитаю агрегацию, а не наследование.
Дизайн для интерфейсов.
Даже если названные шаблоны и принципы частично в некотором смысле частично совпадают, они говорят вам нечто более конкретное (в более конкретной ситуации), чем три вышеуказанных общих принципа.
Хороший ответ @CS. Обобщить,
- DIP является расширением OCP, поэтому
- Когда мы удовлетворяем DIP, мы обычно удовлетворяем и OCP.
- Обратное неверно, и мы можем представить себе OCP-совместимые нарушения DIP. Вот еще один пример (Java).
public abstract class MyClass {
DependencyOne d1;
DependencyTwo d2;
MyClass() {
d1 = new DependencyOne();
d2 = new DependencyTwo();
}
}
OCP доволен, потому что мы можем расширить класс. DIP нарушается, потому что мы напрямую инстанциируем зависимости.
Теперь проблема в том, можем ли мы думать о нарушении OCP, совместимом с DIP. Лучший пример, который я могу придумать, - это аннотация. В Java мы используем@Deprecated
аннотация для обозначения кода, открытого для модификации , тем самым нарушая OCP. В то же время этот код может быть полностью совместим с DIP с точки зрения его абстракций и зависимостей. Некоторые библиотеки используют
Я не могу представить себе пример, совместимый с DIP, но закрытый для расширения, помимо нулевого примера класса, который не имеет зависимостей, что не очень интересно. Я бы сказал, что DIP подразумевает открытость для расширения. Однако могут быть крайние случаи, когда DIP не подразумевает закрытость для модификации.
OCP облегчает потребление зависимого класса. OCP обеспечивает асинхронное использование интерфейса путем отделения старых реализаций от более новых версий. Это позволяет вещам, которые зависят от него, продолжать зависеть от него даже перед лицом изменений для других целей. Таким образом, классу не нужно заботиться о том, кто его называет.
DIP делает пару вещей. Это делает в зависимости от внешних классов легко. Инъекция зависимости позволяет заменить зависимости, поощряя отделение обязанностей по созданию от потребления. Вместо того, чтобы создавать внешнюю зависимость, которая должна использоваться, шаблон заявляет, что он должен быть предоставлен извне. В конечном итоге это поощряет идемпотентный код (код, который не изменяет внешнее состояние). Идемпотентный код хорош, потому что можно убедиться, что он делает только то, что сразу видно. У него нет внешних побочных эффектов. Это очень проверяемое, понятное и читаемое.