- по поводу абстракций - это просто базовый принцип разработки любого ПО, вне зависимости от парадигмы программирования, но часто приписывающийся ООП - сущности должны зависеть от абстракций и не конкретных реализаций. Важен он потому что является основой для параметрического полиморфизма, который является основой большинства паттернов для масштабируемых систем и open-closed principle из SOLID
Спасибо, сняли с языка)
А вот с третьим пунктом не соглашусь:
Почему это важно - при переносе модулей А и Б в другую систему, интерфейс должен быть перенесен вместе с ними так как у них есть от него зависимость, и отсутствие зависимостей делает перенос интерфейса тривиальным.
Делает перенос интерфейса тривиальным как раз его расположение в модуле A, т.к. нам достаточно просто перенести модуль A, ведь он самодостаточен, у него есть все для собственного существования. Если интерфейса в модуле A не будет, то при переносе модуля A нам нужно будет еще дополнительно переносить интерфейс. По этому поводу недавно писал тут https://habr.com/ru/articles/872078/comments/#comment_27770836
В то же время интерфейс может стать частью А или Б в зависимости от конкретной системы, это сугубо ситуативное решение
Если интерфейс станет частью Б, то тогда DIP будет нарушен, т.к. A как зависел от B, так и зависит. От простого добавления абстракции/интерфейса зависимость не меняет направление.
Конечно высокоуровневая. main потребляет реализации других сущностей, поэтому она высокоуровневая. На скриншоте даже видно по неймингу сущностей направление их увеличения/уменьшения уровней. main - HL (High Level) - ML (Medium Level) - LL (Low Level)
...из чего вытекает, что да, трактовка "зависьте от абстракций" была (в 2006 году; в 2017 из Clean Architecture можно сделать другие выводы, почему я и говорю, что важно оговаривать источники) для Мартина допустимым пониманием DIP. Что напрямую противоречит самому началу вашей статьи:
Не противоречит. Как сказано в текущей статье, при выполнении «МВУ не должны зависеть от МНУ» оба модуля будут зависеть от абстракций. МВУ от своей абстракции, а МНУ от абстракции МВУ.
These principles have been described in detail in many different publications [For example, Agile Software Development, Principles, Patterns, and Practices, Robert C. Martin, Prentice Hall, 2002, http://www.butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod, and https://en.wikipedia.org/wiki/SOLID_(object-oriented_design) (or just google SOLID).]
Открываем эту ссылку, и читаем:
DIP - The Dependency Inversion Principle - Depend on abstractions, not on concretions.
Ответная цитата) : из книги «Чистая Архитектура» Часть 3
DIP: The Dependency Inversion Principle The code that implements high-level policy should not depend on the code that implements low-level details. Rather, details should depend on policies.
Выглядит так, что это все-таки допустимая трактовка, не правда ли?
Она и была допустимой, но тут надо понимать, что это не просто абстракции. Другими словами, у этих абстракций есть цель — инвертировать зависимость, чтобы МВУ не зависел от МНУ (это 1ое предложение описания принципа) и это ключевой момент и если он не соблюден, то DIP не выполнен.
Этот поток бесконечных боданий цитатами больше похож на спор ради спора. Настоящих, на мой взгляд, аргументов от вас в пользу того, что текущая статья противоречит действительности, я не услышал. Но все равно спасибо, что благодаря одному из ваших комментариев я подкорректировал определение МВУ, т.к. до этого понять его можно было двояко https://habr.com/ru/articles/872078/comments/#comment_27756172
Ваша трактовка принципа приблизительно ясна. Оказывается есть еще какой-то отдельный 3-й «модуль абстракций», стоящий отдельно от МВУ и МНУ и поэтому каким-то образом где-то происходит инверсия зависимостей. Ок. Я с этим не согласен, но спорить больше не буду)
Да прямо в статье 1996 года и есть такой пример. Вы уверены, что вы слово "модуль" понимаете так же, как Мартин? Потому что в статье, о которой я говорю, нет модуля в вашем понимании этого слова (т.е. чего-то, внутри чего может быть интерфейс), там модуль - это подпрограмма (или класс, или абстрактный класс), там все элементы одного уровня.
"Figure 1 is a “structure chart” . It shows that there are three modules, or subprograms, in the application. The “Copy” module calls the other two. One can easily imagine a loop within the “Copy” module. (See Listing 1.) The body of that loop calls the “Read Keyboard” module to fetch a character from the keyboard, it then sends that character to the “Write Printer” module which prints the character."
Здесь нет какого-то “правильного” ввода определения модуля. Странно, что вы с таким упорством пытаетесь везде показать на тайный смысл слова модуль. Смысловая нагрузка всегда одна — речь идет о наборе связанного функционала, а как его назвать, модуль, или политика, или компонент, или что-то еще, большой разницы нет.
Это значит, что для того, чтобы понять, что означает слово "модуль" в этом определении, нужно ориентироваться на эту статью, а не на "Clean Architecture", где, как я уже говорил, определение другое ("The code that implements high-level policy should not depend on the code that implements low-level details. Rather, details should depend on policies.")
И понимание модуля в статье 1996 года - не такое, как в вашей статье.
Тут аналогично, ответил выше.
Состоит. И из того, что вынесение общих абстракций Мартин приводит следующим пунктом, альтернативным, я делаю вывод, что к моменту "Clean Architecture" (2017 год) его понимание DIP изменилось по сравнению с его же пониманием в 1996. Больше 20 лет прошло, это нормально...
Собственно, я потому и говорю, что объяснения в Clean Architecture - это реткон, потому что к этому моменту уже есть сложившееся использование (достаточно посмотреть в википедию), и надо быть очень акккуратным, чтобы не получить противоречий.
Не увидел каких-то грандиозных противоречий в его формулировках, что координально бы могло повлиять на понимание DIP.
Я уже цитировал Мартина по этому поводу:
One might question why I use the word “inversion”. Frankly, it is because more traditional software development methods, such as Structured Analysis and Design...
Из первой фразы очевидно, что это употребление казалось ему спорным прямо на момент написания этой статьи - но он его, тем не менее, сохранил.
Переводим первую фразу:
Кто-то может задаться вопросом, почему я использую слово “инверсия”.
Не похоже, что из этой фразы можно сделать вывод, что он в чем-то сомневался.
То есть вы мне хотите сказать, что ни перед написанием своей статьи, ни за все время обсуждения вы не потрудились ознакомиться с тем источником, откуда пришло ваше определение?..
Вы это поняли, потому что я сходу не определил, что код который вы привели указан в какой-то из статей? Все листинги наизусть я не помню.
Я же не зря в самом начале говорил про достаточно сложные
DIP от сложности системы не меняется.
Где будут абстракции? Сколько адаптеров нужно написать?
Ответ все тот же: абстракции находятся в границах МВУ, а адаптеры в границах МНУ. Если хотите строго соблюдать DIP, то каждый МВУ должен зависеть сам от себя (от собственной абстракции), т.е. надо сделать так, чтобы мы в любой момент могли, условно, взять МВУ и использовать его в другом проекте, не меняя код самого МВУ. Если мы выносим абстракцию из МВУ отдельно в свое «место для абстракций», то теперь, когда мы захотим использовать наш МВУ в другом проекте, должны будем «тянуть» дополнительную зависимость — абстракцию из нашего отдельного «места для абстракций». Как часто вы внедряете библиотеки для которых нужно дополнительно еще внедрять какие-то абстракции (т.к. без них МВУ не будет работать)? (риторический вопрос)
Если следовать принципу менее строго, то в рамках одного проекта (или, как в вашем случае, в сложной системе), абсолютно нормально (т.к. наши МВУ мы не будем использовать в других проектах) для того, чтобы не плодить множество абстракций и адаптеров выделять это самое «место для абстракций», от которых будут зависеть множество наших МВУ, но это место для абстракций все равно должно принадлежать, пространству, где находятся МВУ.
там модуль - это подпрограмма (или класс, или абстрактный класс)
Где такое сказано? можете привести цитату (Главу, раздел)?
А если открыть Clean Architecture, то там в определении нет слова модуль:
И? Т.е. к чему вы это говорите? Вы хотите сказать, что в DIP нет модулей или что?
Иными словами, "модуль", от которого зависят другие модули, может содержать только абстракции, но не реализации. Ваш МВУ это требование нарушает.
Буквально над вашим комментарием, привел изображение из книги Мартина, где прямо подписано, что на нем изображена инверсия зависимостей. И на этом изображении модуль Entities как-будто бы не состоит из сплошных абстракций... или, по вашему, состоит?
Предположим, что вы правы, и абстракции должны находиться в каком-то собственном 3-ем модуле, тогда в чем заключается инверсия зависимостей? Какая именно зависимость инвертируется и почему именно она? Или название принципа не соответствует его содержанию?
возможно, из Мартиновского кода вам не очевидно, что ReadKeyboard и WritePrinter менять нельзя, именно поэтому в моем примере Console.ReadLine(), который поставляется в стандартной библиотеке
В том то и дело, что вы приводите пример, где нельзя менять потребляемый код. Т.е. для того, чтобы реализовать DIP, очевидно, у вас должен быть доступ и к МВУ и к МНУ.
Что вы хотите сделать с приведенным вами кодом? Вы зачем-то хотите изолировать функции стандартной библиотеки ReadKeyboard() , WritePrinter(c) ? Если да (хотя непонятно для чего), то нужно выносить эти ReadKeyboard() , WritePrinter(c) в обертку, и далее использовать стандартный механизм DIP (например, как на скриншоте выше в предыдущем комментарии)
Опять же, вы говорите о решении какой-то частной задачи с применением DIP, а не о самом DIP. DIP не становится плохим из-за того, что кто-то хочет его применить и это усложняет ему систему.
...а это вы с чего взяли? Это некое допущение, которое вы сделали, и все ваши последующие выводы будут работать только в том случае, если это допущение верно. Но оно же совершенно не обязательно верно.
Это не допущение. Это буквально написано в приведенной вами же цитате: The policies that manage input and output are the lowest-level policies in the system. Т.е. input и output являются крайними в нашей рассматриваемой системе. Даже базовый пример, который отображает работу программы, обычно изображают так: input - [программа] - output
А почему не может быть так, что "потребитель" вызывает и "потребляемого" и input?
См. выше
Нет, нельзя. Простейший пример: прикладной слой вызывает бизнес-слой, и полученный результат выводит в консоль. Прикладной слой - вызывающий, но он же ближе к вводу/выводу.
Судя по содержанию множества ваших комментариев, складывается ощущение, что вы либо тонко троллите, либо у вас какое-то свое особое видение каждого вопроса. Поэтому для того, чтобы ответить на эту часть комментария, поясните, что означают в вашем понимании (т.к. оно наверняка и здесь какое-то особое):
прикладной слой
бизнес-слой
прикладной слой вызывает бизнес-слой, и полученный результат выводит в консоль
Прикладной слой - вызывающий, но он же ближе к вводу/выводу.
Ну то есть у нас сначала библиотека была МНУ, а потом мы добавили еще один модуль, который стал новым МНУ, а библиотека, видимо, еще одним МНУ?
Если я правильно понял ваш кейс, то схема будет выглядеть так:
Т.е. от добавления third-party библиотек реализация DIP не меняется. Если не хотим видоизменять класс BClass, то в МНУ добавляем адаптер, который реализует AInterface и вызывает BClass.b(). В этом случает BClass останется нетронутым. Вообщем, с МНУ можем делать что захотим, главное, чтобы он подстроился под интерфейс AInterface, а как именно он это сделает уже дело десятое.
Хм, ну если мы хотим изолироваться от сторонней библиотеки, то вызов ее методов нужно поместить в МНУ. Если изоляция (обертка над библиотеками) вызовов методов библиотек не нужна, то и DIP к ним применять не нужно.
По вашей же логике, если модуль может зависеть только от модуля, то модуль не может зависеть от абстракции, но во многих своих комментариях вы говорите об обратном (про Оба модуля должны зависеть от абстракций ). Абстракция — это не модуль
То есть автор определяет направление зависимости относительно потока управления
Направление потока управления всегда идет от высокоуровневых сущностей к низкоуровневым, другими словами, выражаясь терминами из текущей статьи, в направлении от потребителей реализации к потребляемым.
МВУ и МНУ при инверсии зависимостей не меняются местами (потребитель реализации как был МВУ, так им и остался), т.е. поток управления остается прежним, а вот сама зависимость между модулями меняет свое направление.
Поэтому, цитата, которую вы привели, не противоречит формулировке в текущей статье.
Про поток управления подробнее написано в 5ой главе книги “Чистая архитектура” в разделе “Dependency inversion”. Вот скриншот оттуда:
зеленой стрелкой показано направление потока управления, а красными стрелками изображены зависимости
Также есть некоторые упоминания потока управления в главе 18 в разделе «The dreaded monolith» («Ужасный монолит»)
Нигде не встречал примеры, где абстракциям выделялось бы какое-то свое определенное место отдельное от рассматриваемых модулей/компонентов.
Если рассматривать систему, как набор модулей/компонентов, то было бы странно увидеть где-то отдельно стоящую абстракцию/интерфейс. Приведу несколько наглядных примеров/изображений из книги «Чистая архитектура»:
Глава 14 Раздел «Breaking the cycle» («Разрыв цикла»). На изображении приведен как раз пример инверсии зависимости (до/после):
В главе 19 об уровнях можно увидеть пример, где интерфейс принадлежит верхнему уровню:
При взаимодействии двух сущностей, одна будет зависеть от другой в любом случае. В DIP нет ни слова про то, что МНУ не должен зависеть от МВУ. В DIP в исходном (“некорректном”) случае МВУ зависел от МНУ, но после применения принципа зависимость инвертировалась, и логично, что МНУ стал зависеть от МВУ.
В книге «Чистая архитектура» в 19 главе в завершающем абзаце вводного раздела сказано:
In a good architecture, the direction of those dependencies is based on the level of the components that they connect. In every case, low-level components are designed so that they depend on high-level components
Переводим:
В хорошей архитектуре направление этих зависимостей обусловлено уровнем компонентов, которые они соединяют. В любом случае низкоуровневые компоненты проектируются так, чтобы они зависели от высокоуровневых компонентов.
Спасибо, сняли с языка)
А вот с третьим пунктом не соглашусь:
Делает перенос интерфейса тривиальным как раз его расположение в модуле A, т.к. нам достаточно просто перенести модуль A, ведь он самодостаточен, у него есть все для собственного существования. Если интерфейса в модуле A не будет, то при переносе модуля A нам нужно будет еще дополнительно переносить интерфейс. По этому поводу недавно писал тут https://habr.com/ru/articles/872078/comments/#comment_27770836
Если интерфейс станет частью Б, то тогда DIP будет нарушен, т.к. A как зависел от B, так и зависит. От простого добавления абстракции/интерфейса зависимость не меняет направление.
Конечно высокоуровневая.
main
потребляет реализации других сущностей, поэтому она высокоуровневая.На скриншоте даже видно по неймингу сущностей направление их увеличения/уменьшения уровней. main - HL (High Level) - ML (Medium Level) - LL (Low Level)
В статье старался избегать академических терминов, чтобы объяснить принцип более доступно
От абстракций модули и у меня зависят. У вас, как я понял, они зависят от отдельного «места/модуля для абстракций». По поводу отдельного «места для абстракций» написал подробнее тут https://habr.com/ru/articles/872078/comments/#comment_27770836
Не противоречит. Как сказано в текущей статье, при выполнении «МВУ не должны зависеть от МНУ» оба модуля будут зависеть от абстракций. МВУ от своей абстракции, а МНУ от абстракции МВУ.
Ответная цитата) :
из книги «Чистая Архитектура» Часть 3
Она и была допустимой, но тут надо понимать, что это не просто абстракции. Другими словами, у этих абстракций есть цель — инвертировать зависимость, чтобы МВУ не зависел от МНУ (это 1ое предложение описания принципа) и это ключевой момент и если он не соблюден, то DIP не выполнен.
Если выражаться более академично, нужно сделать так, чтобы направления потока управления и зависимости смотрели в противоположных направлениях. Об этом, кстати, упомянули тут: https://habr.com/ru/articles/872078/comments/#comment_27764234
Этот поток бесконечных боданий цитатами больше похож на спор ради спора.
Настоящих, на мой взгляд, аргументов от вас в пользу того, что текущая статья противоречит действительности, я не услышал. Но все равно спасибо, что благодаря одному из ваших комментариев я подкорректировал определение МВУ, т.к. до этого понять его можно было двояко https://habr.com/ru/articles/872078/comments/#comment_27756172
Ваша трактовка принципа приблизительно ясна. Оказывается есть еще какой-то отдельный 3-й «модуль абстракций», стоящий отдельно от МВУ и МНУ и поэтому каким-то образом где-то происходит инверсия зависимостей. Ок. Я с этим не согласен, но спорить больше не буду)
Здесь нет какого-то “правильного” ввода определения модуля.
Странно, что вы с таким упорством пытаетесь везде показать на тайный смысл слова модуль.
Смысловая нагрузка всегда одна — речь идет о наборе связанного функционала, а как его назвать, модуль, или политика, или компонент, или что-то еще, большой разницы нет.
Тут аналогично, ответил выше.
Не увидел каких-то грандиозных противоречий в его формулировках, что координально бы могло повлиять на понимание DIP.
Переводим первую фразу:
Не похоже, что из этой фразы можно сделать вывод, что он в чем-то сомневался.
Вы это поняли, потому что я сходу не определил, что код который вы привели указан в какой-то из статей? Все листинги наизусть я не помню.
DIP от сложности системы не меняется.
Ответ все тот же: абстракции находятся в границах МВУ, а адаптеры в границах МНУ.
Если хотите строго соблюдать DIP, то каждый МВУ должен зависеть сам от себя (от собственной абстракции), т.е. надо сделать так, чтобы мы в любой момент могли, условно, взять МВУ и использовать его в другом проекте, не меняя код самого МВУ.
Если мы выносим абстракцию из МВУ отдельно в свое «место для абстракций», то теперь, когда мы захотим использовать наш МВУ в другом проекте, должны будем «тянуть» дополнительную зависимость — абстракцию из нашего отдельного «места для абстракций». Как часто вы внедряете библиотеки для которых нужно дополнительно еще внедрять какие-то абстракции (т.к. без них МВУ не будет работать)? (риторический вопрос)
Если следовать принципу менее строго, то в рамках одного проекта (или, как в вашем случае, в сложной системе), абсолютно нормально (т.к. наши МВУ мы не будем использовать в других проектах) для того, чтобы не плодить множество абстракций и адаптеров выделять это самое «место для абстракций», от которых будут зависеть множество наших МВУ, но это место для абстракций все равно должно принадлежать, пространству, где находятся МВУ.
Где такое сказано? можете привести цитату (Главу, раздел)?
И? Т.е. к чему вы это говорите? Вы хотите сказать, что в DIP нет модулей или что?
Буквально над вашим комментарием, привел изображение из книги Мартина, где прямо подписано, что на нем изображена инверсия зависимостей. И на этом изображении модуль
Entities
как-будто бы не состоит из сплошных абстракций... или, по вашему, состоит?Предположим, что вы правы, и абстракции должны находиться в каком-то собственном 3-ем модуле, тогда в чем заключается инверсия зависимостей? Какая именно зависимость инвертируется и почему именно она? Или название принципа не соответствует его содержанию?
В том то и дело, что вы приводите пример, где нельзя менять потребляемый код. Т.е. для того, чтобы реализовать DIP, очевидно, у вас должен быть доступ и к МВУ и к МНУ.
Что вы хотите сделать с приведенным вами кодом? Вы зачем-то хотите изолировать функции стандартной библиотеки
ReadKeyboard()
,WritePrinter(c)
?Если да (хотя непонятно для чего), то нужно выносить эти
ReadKeyboard()
,WritePrinter(c)
в обертку, и далее использовать стандартный механизм DIP (например, как на скриншоте выше в предыдущем комментарии)Опять же, вы говорите о решении какой-то частной задачи с применением DIP, а не о самом DIP. DIP не становится плохим из-за того, что кто-то хочет его применить и это усложняет ему систему.
Это не допущение. Это буквально написано в приведенной вами же цитате:
The policies that manage input and output are the lowest-level policies in the system.
Т.е. input и output являются крайними в нашей рассматриваемой системе.
Даже базовый пример, который отображает работу программы, обычно изображают так: input - [программа] - output
См. выше
Судя по содержанию множества ваших комментариев, складывается ощущение, что вы либо тонко троллите, либо у вас какое-то свое особое видение каждого вопроса.
Поэтому для того, чтобы ответить на эту часть комментария, поясните, что означают в вашем понимании (т.к. оно наверняка и здесь какое-то особое):
Если я правильно понял ваш кейс, то схема будет выглядеть так:
Т.е. от добавления third-party библиотек реализация DIP не меняется.
Если не хотим видоизменять класс
BClass
, то в МНУ добавляем адаптер, который реализуетAInterface
и вызываетBClass.b()
. В этом случаетBClass
останется нетронутым. Вообщем, с МНУ можем делать что захотим, главное, чтобы он подстроился под интерфейсAInterface
, а как именно он это сделает уже дело десятое.Не совсем понял к чему вопрос. Что почему? Я предыдущий комментарий отредактировал, поэтому, возможно, вопрос относится к старой его версии.
Вот тут оставил комментарий по такой же теме (о местонахождении интерфейса)
https://habr.com/ru/articles/872078/comments/#comment_27767846
Хм, ну если мы хотим изолироваться от сторонней библиотеки, то вызов ее методов нужно поместить в МНУ. Если изоляция (обертка над библиотеками) вызовов методов библиотек не нужна, то и DIP к ним применять не нужно.
В модуле нижнего уровня
Речь не про какие-то сложные системы, речь про DIP.
Если DIP сильно усложняет систему, то никто не обязывает вас его использовать
По вашей же логике, если
модуль может зависеть только от модуля
, то модуль не может зависеть от абстракции, но во многих своих комментариях вы говорите об обратном (проОба модуля должны зависеть от абстракций
). Абстракция — это не модульТут привели определение из книги «Чистая архитектура»: https://habr.com/ru/articles/872078/comments/#comment_27764234
Направление потока управления всегда идет от высокоуровневых сущностей к низкоуровневым, другими словами, выражаясь терминами из текущей статьи, в направлении от потребителей реализации к потребляемым.
МВУ и МНУ при инверсии зависимостей не меняются местами (потребитель реализации как был МВУ, так им и остался), т.е. поток управления остается прежним, а вот сама зависимость между модулями меняет свое направление.
Поэтому, цитата, которую вы привели, не противоречит формулировке в текущей статье.
Про поток управления подробнее написано в 5ой главе книги “Чистая архитектура” в разделе “Dependency inversion”.
Вот скриншот оттуда:
Также есть некоторые упоминания потока управления в главе 18 в разделе «The dreaded monolith» («Ужасный монолит»)
Нигде не встречал примеры, где абстракциям выделялось бы какое-то свое определенное место отдельное от рассматриваемых модулей/компонентов.
Если рассматривать систему, как набор модулей/компонентов, то было бы странно увидеть где-то отдельно стоящую абстракцию/интерфейс.
Приведу несколько наглядных примеров/изображений из книги «Чистая архитектура»:
Глава 14 Раздел «Breaking the cycle» («Разрыв цикла»). На изображении приведен как раз пример инверсии зависимости (до/после):
В главе 19 об уровнях можно увидеть пример, где интерфейс принадлежит верхнему уровню:
Тут согласен) Ставить во главу угла SOLID, как набор важнейших принципов, я бы тоже не стал.
При взаимодействии двух сущностей, одна будет зависеть от другой в любом случае. В DIP нет ни слова про то, что МНУ не должен зависеть от МВУ.
В DIP в исходном (“некорректном”) случае МВУ зависел от МНУ, но после применения принципа зависимость инвертировалась, и логично, что МНУ стал зависеть от МВУ.
В книге «Чистая архитектура» в 19 главе в завершающем абзаце вводного раздела сказано:
Переводим: