SOLID: Означает ли DIP, что композиция / агрегация к объекту запрещена?
Я пытаюсь понять и применять твердые принципы. Относится ли принцип инверсии зависимости, означает ли это, что составление / агрегирование объекта запрещено? Чтобы интерфейс всегда использовался для доступа к другому методу класса?
Я имею в виду, что:
class ServiceClass {
void serviceClasshelper();
}
class MainClass {
void MainClass(ServiceClass service); // To use serviceClasshelper
}
Должен быть изменен на:
class ServiceInterface {
virtual void interfaceHelper() =0;
}
class ServiceClass : public ServiceInterface {
void serviceClasshelper();
void interfaceHelper() { serviceClasshelper(); };
}
class MainClass {
void MainClass(ServiceInterface service); // Uses interfaceHelper
}
Я думаю (или, по крайней мере, я надеюсь), что я понимаю принцип. Но интересно, если это можно перефразировать так. Действительно, материал, который я читал о DIP, предлагает использовать интерфейсы.
Спасибо!
2 ответа
По сути, основная идея DIP:
- Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций.
- Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
Как видите, он говорит, что должен, а не должен. Это не запрещает вам делать что-либо.
Если ваши занятия composite of / aggregate to
другие специфические classes
(не interfaces / abstract classes
) это нормально! Ваш код по-прежнему будет компилироваться и запускаться, не будет отображаться никаких предупреждений о том, что "эй, вы нарушаете DIP". Поэтому я думаю, что ответ на ваш вопрос: нет, это не так.
Представьте, что ваша система состоит из тысячи классов, вы можете просто применить DIP к 2 классам, и один из них зависит от 3-го конкретного класса (не interfaces / abstract classes
). Пока ваша проблема решена, ничего не имеет значения. Поэтому постарайтесь сделать ваше решение коротким и простым -> легким для понимания. Поверьте мне, вы найдете это ценным через 1 месяц после того, как вы оглянетесь на свое решение.
DIP - это руководство, которое говорит вам, что делать, когда вы сталкиваетесь с определенным набором проблем, чтобы решить их. Это не волшебный ориентир, он имеет свою цену: сложность. Чем больше вы применяете DIP, тем сложнее будет ваша система. Так что используйте это с умом. Для дальнейшей поддержки этого пункта, я рекомендую вам взглянуть на эту ссылку (взято из Head First: Design Patterns
книга). Перейдите по этой ссылке (это файл PDF) и на верхней панели перейдите на страницу 635 / 681
, Или, если вы достаточно ленивы, просто прочитайте цитату ниже:
Ваш разум на шаблонах
Начинающий использует шаблоны повсюду. Это хорошо: новичок получает большой опыт и практикуется с использованием шаблонов. Начинающий также думает: "Чем больше шаблонов я использую, тем лучше дизайн". Начинающий узнает, что это не так, что все дизайны должны быть максимально простыми. Сложность и шаблоны должны использоваться только там, где они необходимы для практической расширяемости.
По мере продвижения обучения Промежуточный разум начинает видеть, где нужны шаблоны, а где нет. Промежуточный разум все еще пытается втиснуть слишком много квадратных узоров в круглые отверстия, но также начинает понимать, что узоры могут быть адаптированы к ситуации, когда канонический узор не подходит.
Разум дзен способен видеть шаблоны там, где они подходят естественным образом. Ум дзен не одержим использованием шаблонов; скорее он ищет простые решения, которые лучше всего решают проблему. Разум дзен мыслит в терминах объектных принципов и их компромиссов. Когда естественным образом возникает потребность в паттерне, ум дзэн применяет его, хорошо зная, что может потребоваться адаптация. Разум дзен также видит отношения к подобным образцам и понимает тонкости различий в намерениях связанных образцов. Разум дзен - это тоже ум новичка - он не позволяет всем этим знаниям шаблона чрезмерно влиять на проектные решения.
Наконец, я укажу на шаблон проектирования Gang of Four, в котором используется DIP: стратегия
Пример задачи: A Character
Можно использовать 3 вида оружия: Hand
, Sword
& Gun
, Он (Character
) может поменять свое текущее оружие, когда захочет.
Анализ: это очень типичная проблема. Сложная задача - как обмениваться оружием во время выполнения.
Кандидатское решение со Стратегией: (просто набросок):
weapon = new Hand();
weapon.Attack(); // Implementation of Hand class
weapon = new Sword();
weapon.Attack(); // Implementation of Sword class
weapon = new Gun();
weapon.Attack(); // Implementation of Gun class
Другие шаблоны проектирования и структуры, которые используют DIP:
- Инверсия Контроля (иначе IoC)
- Внедрение зависимости
- Autofac Framework (вы можете скачать и играть с ним)
Да, это верно, DIP говорит, что вам нужно зависеть от абстракции (интерфейса), а не от конкреции (класса с реальной реализацией).
Идея в том, что если вы зависите от ServiceClass
Вы зависите от этой конкретной реализации и не можете легко заменить ее другой реализацией. Например, у вас есть AnotherServiceClass
, но использовать его вместо ServiceClass
, вам придется унаследовать его от ServiceClass
что может быть нежелательно или даже невозможно. Имея зависимость от интерфейса, вы можете легко это сделать.
Обновление: вот более конкретный пример, иллюстрирующий идею выше
// Service class does something useful (sendNetworkRequest)
// and is able to report the results
class ServiceClass {
void reportResults();
void sendNetworkRequest();
}
// Main logger collects results
class MainLogger {
void registerService(ServiceClass service);
}
У нас есть ServiceClass
который мы переходим к MainLogger::registerService
регистратор (например) вызывает service->reportResults()
периодически и сохраняет в файл.
Теперь представьте, что у нас есть другой сервис:
// Service class does something useful (calculateYearlyReport)
// and is able to report the results
class AnotherServiceClass {
void reportResults();
void calculateYearlyReport();
}
Если мы просто используем конкретные классы здесь и наследуем AnotherServiceClass
от ServiceClass
мы сможем передать его MainLogger::registerServcie
но кроме нарушения DIP
мы тоже будем нарушать LSP
(поскольку подкласс не может использоваться в качестве замены BaseClass) и ISP
(поскольку у нас явно есть разные интерфейсы - один для отчета о результатах, а другой для выполнения полезной работы), и вы также нарушите еще одно хорошее правило, предпочитая композицию наследованию.
Еще один недостаток передачи экземпляра ServiceClass
прямо как сейчас, вы не можете гарантировать, что MainLogger
не полагается на внутреннюю структуру (доступ к другим методам / членам и т. д.).
Еще одним моментом является простота обслуживания кода - когда вы видите, что интерфейс передан, вам просто нужно просмотреть "протокол" связи между двумя объектами. В то время как с конкретным классом, вам на самом деле нужно пройти через реализацию, чтобы понять, как используется переданный объект.
Таким образом, в общем случае лучше следовать SOLID и другим принципам ООП, где это возможно, это делает код чище и проще в поддержке, а также позволяет избежать ошибок, которые впоследствии будет трудно исправить.