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:

Да, это верно, 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 и другим принципам ООП, где это возможно, это делает код чище и проще в поддержке, а также позволяет избежать ошибок, которые впоследствии будет трудно исправить.

Другие вопросы по тегам