Аналогии нужны для того, чтобы доступнее объяснять сухие академические термины.
Неа. Вполне можно объяснить термины без аналогий.
Функция input() является крайней в стеке вызовов
...а это вы с чего взяли?
Это некое допущение, которое вы сделали, и все ваши последующие выводы будут работать только в том случае, если это допущение верно. Но оно же совершенно не обязательно верно.
Теперь, давайте введем две функции: потребитель() и потребляемый(). Из функции потребитель() вызывается функция потребляемый(). Т.е. потребитель() использует/вызывает/потребляет реализацию функции потребляемый().
А почему не может быть так, что "потребитель" вызывает и "потребляемого" и input?
Возвращаясь к определениям из текущей статьи и исходя из вышесказанного можно смело утверждать, что расстояние до МВУ (до потребителя реализаций другого модуля) будет всегда больше, чем до МНУ
Нет, нельзя. Простейший пример: прикладной слой вызывает бизнес-слой, и полученный результат выводит в консоль. Прикладной слой - вызывающий, но он же ближе к вводу/выводу.
Чтобы была инверсия, модули должны остаться теми же самыми, а поменяться только направление зависимостей. Я же утверждаю, что если пользоваться введенной в посте терминологией, модуль, содержащий А, станет "МНУ", модуль, содержащий Б, станет "МВУ", и "МВУ" продолжит зависеть от "МНУ".
МНУ не используется интерфейс из МВУ, а реализует его, то есть зависит от абстракции - интерфейс.
Я уже выше объяснял, почему это не соответствует терминологии, введенной в посте, так что попробуем на примере.
Вот у вас есть интерфейс
IOrderRepository
{
void SaveOrder(Order order)
}
И IOrderRepository, и Order определены в МВУ, потому что больше негде. Когда в МНУ вводится реализация Repository: IOrderRepository, она внутри себя неизбежно обратится к свойствам Order. Обращение к свойствам объекта - это использование, как ни крути. Это значит, что МНУ в этом примере используетМВУ. Но DIP не нарушен.
Мне кажется, что не существует бизнес проблем которые формулируются как
невозможно переиспользовать А в разных ситуациях (с разными реализациями Б)
Это зависит от того, чем занимается ваш бизнес. Если, например, он продает системы управления продажами, то может быть бизнес-задача "хочу использовать одну и ту же систему с разными службами доставки". Это типичное "переиспользуем А" (юз-кейс продажи) с разными Б (службами доставки).
Если ваше бизнес решение с лёгкостью позволяет заменить базу данных на другую, то вероятно вам база данных в общем то избыточна и можно обойтись более простыми == дешёвыми средствами.
Вы не поверите, но система, в разработке которой я участвую, реально позволяет работать с двумя разными СУБД (а сейчас еще и третью добавляют). Это было непросто сделать, но это сделали. И нет, БД там не избыточна.
Да я знаю есть ещё юнит тесты опять же ИМХО для бизнеса нужны не они, а функциональные тесты которым наличие абстракций ни как не помогает.
Это не больше чем YHO. Есть немало сценариев, когда юнит-тесты (или компонентные тесты) весьма и весьма эффективны.
Я сторонник фэйкания внешних зависимостей вместо мокания внутренних реализаций.
Во-первых, для этого, внезапно, тоже используется абстракция, просто совершенно другого уровня. А во-вторых, есть зависимости, которые слишком дорого мокать, когда вам надо гонять сотни тестов параллельно.
В таком случае это не "чтобы работать с библиотекой", а "чтобы изменить поведение". Это валидный сценарий, но он как раз показывает неудобство терминов "верхний" и "нижний" модуль - потому что на самом деле, отношение модуля пользователя к вашей библиотеке не зависит от того, передал ли он вам имплементацию форматтера или нет.
Вот у меня есть модуль (либа) на гитхабе со своим интерфэйсом, пользователь этой либы будет реализовывать интерфэйс либы чтобы с ней работать, то есть модуль пользователя будет нижним модулем, а либа будет верхним модулем. Всё логично, моя либа полностью соответствует DIP.
Не факт, что соответствует. В норме для того, чтобы работать с библиотекой, не должно быть нужно реализовывать интерфейсы из этой библиотеки. Но зависит, конечно, от сценария использования.
то есть модуль пользователя будет нижним модулем, а либа будет верхним модулем
Обычно как раз наоборот, знаете ли. Если мы говорим про библиотеки именно.
У меня или у Мартина? Моя формулировка не имеет отношения к делу, потому что я не использую "нижний" или "верхний" для DIP, меня это не волнует (и так, на самом деле, намного проще жить).
Мартин же изначально это никак не расшифровывает (для него просто очевидно, что операция копирования - это high-level policy, а примитивы чтения и записи - low level detailed modules). Позже, в Clean Architecture он это расшифровывает следующим образом:
A strict definition of “level” is “the distance from the inputs and outputs.” The farther a policy is from both the inputs and the outputs of the system, the higher its level. The policies that manage input and output are the lowest-level policies in the system.
Эта расшифровка полностью совместима с его примером в работе 1996 года.
Но тогда, где будет инверсия зависимости? Т.е., в этом случае название принципа, как-будто бы не имеет отношения к его содержанию.
"One might question why I use the word “inversion”. Frankly, it is because more traditional software development methods, such as Structured Analysis and Design, tend to create software structures in which high level modules depend upon low level modules, and in which abstractions depend upon details. Indeed one of the goals of these methods is to define the subprogram hierarchy that describes how the high level modules make calls to the low level modules. [...] Thus, the dependency structure of a well designed object oriented program is “inverted” with respect to the dependency structure that normally results from traditional procedural methods." ("The Dependency Inversion Principle", Robert C. Martin, C++ Report, May 1996)
ничего не проигнорировал, т.к. в его примерах оба модуля зависят от интерфейса
Неа. У автора интерфейс находится внутри модуля, поэтому тот модуль, который его включает, от этого интерфейса не зависит. Вот потребитель (некий A) - зависит. А модуль, в котором A описан (МВУ) - нет.
Или вы предлагаете сделать 3-ий модуль в котором лежит только эта абстракция и оба других модуля зависят от этого третьего?
Не я, а описание принципа.
Тут я хочу тогда спросить, а какую проблему решаем?
Оригинальное описание принципа решает проблему "невозможно переиспользовать А в разных ситуациях (с разными реализациями Б)".
Лично я с помощью этого принципа еще решаю чисто технологические вещи типа "А лежит в пакете Х, А нужно использовать Б, Б лежит в пакет У, Б нужна функциональность из пакета Х - циклическая зависимость пакетов".
Не разводить ООП с фасадами, интерфейсами, реализациями и DI для выбора какой-то из них, а просто вынести куда-то методы для работы с ней и вызывать библиотеку только в них.
Во-первых, вы только что описали фасад.
Во многих случаях реализация одна-единственная и обновление/замена библиотеки сведётся к изменению только кода этих методов.
Во-вторых, часто когда считают реализации забывают про необходимость их подмены при тестировании. Не буду спорить, кому-то это правда не нужно; но ситуаций, когда об этом не подумали, а потом нужно рефакторить код, чтобы добавить тесты, я в своей жизни встречал больше, чем мне бы хотелось.
Здесь аналогично, речь про зависимости, а не «использование реализации» Обратите здесь особое внимание на слово «реализацию».
Я бы в это поверил, если бы дальше в этом же определении не было:
модуль верхнего уровня — это модуль, который находится выше в «пищевой цепочке», т.е. потребляет (имеет внутри себя) код другого модуля.
Поскольку нигде в ваших примерах напрямую код не включается, речь, очевидно, идет про ссылку (т.е. один модуль ссылается на элемент другого модуля). В вашей финальной реализации ваш "МНУ" неизбежно ссылается на "МВУ" (потому что в нем определен интерфейс), значит, в вашей терминологии он стал модулем верхнего уровня.
Я вам честно скажу, не надо пытаться выкрутиться из этой ситуации, разруливая понятия "зависит" и "использует" - выход из нее состоит в том, чтобы не привязывать "верхний" и "нижний" уровни к использованию вообще (потому что нигде у Мартина такой привязки я не видел).
Тут еще вопрос что верхнее, а что нижнее. На диаграимах UML родительское сверху рисуют, а тут МВУ это то что использует готовое. Полная инверсия, короче.
И где должен размещаться код этого адаптера?
Я, впрочем, не зря сказал про сложные системы: в них просто не хватит ресурсов писать адаптеры на каждую абстракцию. Пример с логгерами привести?
Неа. Вполне можно объяснить термины без аналогий.
...а это вы с чего взяли?
Это некое допущение, которое вы сделали, и все ваши последующие выводы будут работать только в том случае, если это допущение верно. Но оно же совершенно не обязательно верно.
А почему не может быть так, что "потребитель" вызывает и "потребляемого" и
input
?Нет, нельзя. Простейший пример: прикладной слой вызывает бизнес-слой, и полученный результат выводит в консоль. Прикладной слой - вызывающий, но он же ближе к вводу/выводу.
В какой терминологической системе? Лично вашей, вами введенной, или используемой в литературе (например, у Мартина)?
Чтобы была инверсия, модули должны остаться теми же самыми, а поменяться только направление зависимостей. Я же утверждаю, что если пользоваться введенной в посте терминологией, модуль, содержащий
А
, станет "МНУ", модуль, содержащийБ
, станет "МВУ", и "МВУ" продолжит зависеть от "МНУ".Я уже выше объяснял, почему это не соответствует терминологии, введенной в посте, так что попробуем на примере.
Вот у вас есть интерфейс
И
IOrderRepository
, иOrder
определены вМВУ
, потому что больше негде. Когда вМНУ
вводится реализацияRepository: IOrderRepository
, она внутри себя неизбежно обратится к свойствамOrder
. Обращение к свойствам объекта - это использование, как ни крути. Это значит, чтоМНУ
в этом примере используетМВУ
. Но DIP не нарушен.Это зависит от того, чем занимается ваш бизнес. Если, например, он продает системы управления продажами, то может быть бизнес-задача "хочу использовать одну и ту же систему с разными службами доставки". Это типичное "переиспользуем А" (юз-кейс продажи) с разными Б (службами доставки).
Вы не поверите, но система, в разработке которой я участвую, реально позволяет работать с двумя разными СУБД (а сейчас еще и третью добавляют). Это было непросто сделать, но это сделали. И нет, БД там не избыточна.
Это не больше чем YHO. Есть немало сценариев, когда юнит-тесты (или компонентные тесты) весьма и весьма эффективны.
Во-первых, для этого, внезапно, тоже используется абстракция, просто совершенно другого уровня. А во-вторых, есть зависимости, которые слишком дорого мокать, когда вам надо гонять сотни тестов параллельно.
У вас модули несопоставимых размеров. Если библиотека - модуль, то вызывающий ее модуль - не Program, а все приложение целиком.
Я, собственно, поэтом и говорю, что разделение на "МВУ" и "МНУ" в статье лишено какой-либо пользы (а вот вред может принести).
В таком случае это не "чтобы работать с библиотекой", а "чтобы изменить поведение". Это валидный сценарий, но он как раз показывает неудобство терминов "верхний" и "нижний" модуль - потому что на самом деле, отношение модуля пользователя к вашей библиотеке не зависит от того, передал ли он вам имплементацию форматтера или нет.
Не факт, что соответствует. В норме для того, чтобы работать с библиотекой, не должно быть нужно реализовывать интерфейсы из этой библиотеки. Но зависит, конечно, от сценария использования.
Обычно как раз наоборот, знаете ли. Если мы говорим про библиотеки именно.
Только если ваш фасад не статический. К сожалению, обычно люди, которые "без интерфейсов, реализаций и DI", делают статический.
Конечно, не для всего. Просто нужно осознавать достоинства и недостатки принимаемых решений.
Да нет, это совершенно нормальная ситуация. Это же книжное описание полиморфизма (одного из).
Проблема с аналогиями в том, что они врут.
У меня или у Мартина? Моя формулировка не имеет отношения к делу, потому что я не использую "нижний" или "верхний" для DIP, меня это не волнует (и так, на самом деле, намного проще жить).
Мартин же изначально это никак не расшифровывает (для него просто очевидно, что операция копирования - это high-level policy, а примитивы чтения и записи - low level detailed modules). Позже, в Clean Architecture он это расшифровывает следующим образом:
Эта расшифровка полностью совместима с его примером в работе 1996 года.
"One might question why I use the word “inversion”. Frankly, it is because more traditional software development methods, such as Structured Analysis and Design, tend to create software structures in which high level modules depend upon low level modules, and in which abstractions depend upon details. Indeed one of the goals of these methods is to define the subprogram hierarchy that describes how the high level modules make calls to the low level modules. [...] Thus, the dependency structure of a well designed object oriented program is “inverted” with respect to the dependency structure that normally results from traditional procedural methods." ("The Dependency Inversion Principle", Robert C. Martin, C++ Report, May 1996)
Мартин в свое время объяснял, почему этот принцип назван так. С тех пор уже прижилось.
Ну не знаю, я регулярно вижу сборки, которые содержат только абстракции.
Неа. У автора интерфейс находится внутри модуля, поэтому тот модуль, который его включает, от этого интерфейса не зависит. Вот потребитель (некий
A
) - зависит. А модуль, в которомA
описан (МВУ
) - нет.Не я, а описание принципа.
Оригинальное описание принципа решает проблему "невозможно переиспользовать А в разных ситуациях (с разными реализациями Б)".
Лично я с помощью этого принципа еще решаю чисто технологические вещи типа "А лежит в пакете Х, А нужно использовать Б, Б лежит в пакет У, Б нужна функциональность из пакета Х - циклическая зависимость пакетов".
Во-первых, вы только что описали фасад.
Во-вторых, часто когда считают реализации забывают про необходимость их подмены при тестировании. Не буду спорить, кому-то это правда не нужно; но ситуаций, когда об этом не подумали, а потом нужно рефакторить код, чтобы добавить тесты, я в своей жизни встречал больше, чем мне бы хотелось.
Я бы в это поверил, если бы дальше в этом же определении не было:
Поскольку нигде в ваших примерах напрямую код не включается, речь, очевидно, идет про ссылку (т.е. один модуль ссылается на элемент другого модуля). В вашей финальной реализации ваш "МНУ" неизбежно ссылается на "МВУ" (потому что в нем определен интерфейс), значит, в вашей терминологии он стал модулем верхнего уровня.
Я вам честно скажу, не надо пытаться выкрутиться из этой ситуации, разруливая понятия "зависит" и "использует" - выход из нее состоит в том, чтобы не привязывать "верхний" и "нижний" уровни к использованию вообще (потому что нигде у Мартина такой привязки я не видел).
Вот поэтому я и пишу, что поменялось местами.