по поводу абстракций - это просто базовый принцип разработки любого ПО, [...] сущности должны зависеть от абстракций и не конкретных реализаций
Любопытно, кстати. Не знаете ли вы, случайно, где этот принцип был сформулирован (в таком виде) в первый раз? Потому что все упоминания, которые мне удалось найти - как раз в DIP.
Есть связанный, но отличающийся, принцип в GoF: "Program to an interface, not an implementation." Это 1994 год, это и просто по дате предшествует статье Мартина, и в его статье есть ссылки на GoF, так что он, очевидно, был в курсе этой работы; но это, все-таки, другой принцип.
Нет, это именно просто абстракции. Нет, согласно двум приведенным мной цитатам это не обязательно.
На всякий случай добавлю, что да, необходимо, чтобы после введения абстракций ранее существовавшая зависимость модуля верхнего уровня от модуля нижнего уровня была разорвана, иначе абстракция действительно не несет пользы. Но не более того.
Конечно высокоуровневая. main потребляет реализации других сущностей, поэтому она высокоуровневая.
Гм.
Что такое, по вашему, main на этой диаграмме? Какому конкретно месту в коде традиционной программы оно соответствует? Что оно делает? Кто его вызывает?
Она и была допустимой, но тут надо понимать, что это не просто абстракции.
Нет, это именно просто абстракции. Это явно написано в двух приведенных цитатах (особенно, в первой, в книге 2006 года).
Если выражаться более академично, нужно сделать так, чтобы направления потока управления и зависимости смотрели в противоположных направлениях.
Нет, согласно двум приведенным мной цитатам это не обязательно.
Настоящих, на мой взгляд, аргументов от вас в пользу того, что текущая статья противоречит действительности, я не услышал.
Это меня не удивляет. Ведь иначе нельзя утверждать в заголовке, что 90% разработчиков чего-то не понимают.
Я повторюсь еще раз: вы утверждаете, что зависимости от абстракций, а не от реализаций, недостаточно для DIP. Цитаты из самого Мартина (кроме Clean Architecture, но вы не считаете, что она чему-то противоречит) говорят, что этого достаточно. Мой опыт тоже говорит, что достаточно. Так что, извините, я продолжу считать, что громкий заголовок вашей статьи не находит подтверждения в оригинальных работах.
Ваша трактовка принципа приблизительно ясна. Оказывается есть еще какой-то отдельный 3-й «модуль абстракций», стоящий отдельно от МВУ и МНУ
Не обязательно есть, а может быть, и это не нарушает принципа (и даже разумно и оправдано, см. цитаты из википедии и самого Мартина).
Здесь нет какого-то “правильного” ввода определения модуля. [...] Смысловая нагрузка всегда одна — речь идет о наборе связанного функционала
Да нет же. Подпрограмма (или класс) - это не набор связанного функционала. Это просто подпрограмма.
Не увидел каких-то грандиозных противоречий в его формулировках, что координально бы могло повлиять на понимание DIP.
Ну, если вы не видите, значит точно можно пользоваться ранним определением, в котором явно написано, что можно зависеть от абстракций. Это прямо противоречит вашей статье.
Не похоже, что из этой фразы можно сделать вывод, что он в чем-то сомневался.
Если бы он не сомневался, он бы не писал "можно задаться вопросом". Риторические обороты - такие риторические.
Вы это поняли, потому что я сходу не определил, что код который вы привели указан в какой-то из статей?
Я явным образом написал, откуда он, даже определять не надо.
DIP от сложности системы не меняется.
Принцип - не меняется. А то, как он применяется - меняется. Вы же не думаете, что у него ровно один вариант применения?
Если хотите строго соблюдать DIP, то каждый МВУ должен зависеть сам от себя (от собственной абстракции), т.е. надо сделать так, чтобы мы в любой момент могли, условно, взять МВУ и использовать его в другом проекте, не меняя код самого МВУ.
Это не является строгим требованием DIP (я уже приводил цитаты).
И именно потому, что это чрезвычайно избыточно, я и не рекомендую так делать.
Как часто вы внедряете библиотеки для которых нужно дополнительно еще внедрять какие-то абстракции (т.к. без них МВУ не будет работать)?
Каждый раз, когда начинаю проект?.. Вы, наверное, не слышали про Microsoft.Extensions.*?
но это место для абстракций все равно должно принадлежать, пространству, где находятся МВУ.
Но зачем? Это не позволит переиспользовать интеграционный код, который зависит от этих абстракций, в других проектах, не связанных с этими "МВУ".
Вы действительно не видите намеков на cross-cutting concerns, даже когда я прямым текстом про логирование написал?
Направление потока управления всегда идет от высокоуровневых сущностей к низкоуровневым, другими словами, выражаясь терминами из текущей статьи, в направлении от потребителей реализации к потребляемым.
Не всегда.
Посмотрите на иллюстрацию 5.1 из той же главы:
main - не высокоуровневая сущность. Более того, в консольной программе main может напрямую получить данные на вход, и дальше передать их в сущность более высокого уровня, та - в более высокий уровень и так далее. И это не только в консолях будет так: во многих современных программах entry point (не важно, будь это lambda function handler, или web controller, или event handler) - это элемент уровня представления, и он же - начало потока управления (по крайней мере, той чего части, которая контролируется и понятна разработчику).
И в таких программах поток управления будет идти, грубо говоря, от какого-нибудь LLx, где сообщение пришло в систему, дальше в соответствующий MLx, который отвечает за обработку сценария, оттуда будет вызван HLx, который бизнес-сущности, а дальше в обратном порядке - сценарий (MLx) и обработчик ответа (LLy).
Я, прямо скажем, вообще не могу вспомнить ни одной системы, которая бы начинала свою работу с вызова доменного объекта (но я, понятное дело, работаю с определенным подмножеством систем).
Вот вам еще про этот же принцип, на этот раз из "Agile Principles, Patterns, and Practices in C#" (Micah Martin, Robert C. Martin, 2006) (к сожаление, у меня нет под рукой "Agile Software Development, Principles, Patterns, and Practices" (Robert C. Martin, Prentice Hall, 2002), но как можно будет дальше видеть, это не должно быть принципиально). В разделе 11, "The Dependency-Inversion Principle (DIP)" идет долгий (и почти дословный) пересказ кусков статьи 1996 года (именно поэтому я не думаю, что в книге 2002 есть фундаментальные отличия; забавно, что там опять есть объяснение "Over the years, many have questioned why I use the word inversion in the name of this principle"), после которого идет более интересный нам фрагмент "Ownership Inversion".
Note that the inversion here is one of not only dependencies but also interface ownership. We often think of utility libraries as owning their own interfaces. But when DIP is applied, we find that the clients tend to own the abstract interfaces and that their servers derive from them. [...] In this context, ownership simply means that the owned interfaces are distributed with the owning clients and not with the servers that implement them. The interface is in the same package or library with the client. This forces the server library or package to depend on the client library or package.
Это совпадает с тем, что говорите вы... но вот вам следующий абзац:
Of course, there are times when we don’t want the server to depend on the client. This is especially true when there are many clients but only one server. In that case, the clients must agree on the service interface and publish it in a separate package.
И это то, о чем говорю я - когда модули зависят не друг от друга, а от абстракций.
И, все оттуда же, буквально следующий абзац:
A somewhat more naive, yet still very powerful, interpretation of DIP is the simple heuristic: “Depend on abstractions.”
...из чего вытекает, что да, трактовка "зависьте от абстракций" была (в 2006 году; в 2017 из Clean Architecture можно сделать другие выводы, почему я и говорю, что важно оговаривать источники) для Мартина допустимым пониманием DIP. Что напрямую противоречит самому началу вашей статьи:
Зачастую, когда речь заходит про принцип инверсии зависимостей, можно услышать, что инверсия зависимостей (далее DIP) — это что-то там про зависимость от абстракций [...] Но такая трактовка принципа в корне неверна.
Что вы хотите сделать с приведенным вами кодом? Вы зачем-то хотите изолировать функции стандартной библиотеки ReadKeyboard() , WritePrinter(c) ? Если да (хотя непонятно для чего)
То есть вы мне хотите сказать, что ни перед написанием своей статьи, ни за все время обсуждения вы не потрудились ознакомиться с тем источником, откуда пришло ваше определение?..
то нужно выносить эти ReadKeyboard() , WritePrinter(c) в обертку
Можно.
Но вопрос в том, насколько специфичной будет эта обертка (и ее интерфейсы). Я же не зря в самом начале говорил про достаточно сложные системы: в изолированном примере у меня есть только политика Copy, но в системе у меня будет еще и политика Timestamp (которая к каждой строке из ввода добавляет время, и выплевывает в вывод), и политика Split, которая будет брать один ввод и делить его на два вывода. Каждая такая политика - отдельный модуль, и они ничего не знают друг о друге.
Где будут абстракции? Сколько адаптеров нужно написать?
Опять же, вы говорите о решении какой-то частной задачи с применением DIP, а не о самом DIP.
Я, гм, говорю о задаче, которая приведена в фундаментальной статье про DIP как иллюстрация этого принципа.
DIP не становится плохим из-за того, что кто-то хочет его применить и это усложняет ему систему.
Именно поэтому я стараюсь не усложнять системы, прикрываясь DIP, а делать их проще, не противореча ему.
Где такое сказано? можете привести цитату (Главу, раздел)?
"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."
("The Dependency Inversion Principle", Robert C. Martin, C++ Report, May 1996; раздел "Example: the “Copy” program.")
И? Т.е. к чему вы это говорите? Вы хотите сказать, что в DIP нет модулей или что?
Я это говорю к тому, что в вашей статье приведено вот такое определение DIP:
Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба модуля должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
Это определение дословно соответствует определению, данному в статье 1996 года:
A. HIGH LEVEL MODULES SHOULD NOT DEPEND UPON LOW LEVEL MODULES. BOTH SHOULD DEPEND UPON ABSTRACTIONS. B. ABSTRACTIONS SHOULD NOT DEPEND UPON DETAILS. DETAILS SHOULD DEPEND UPON ABSTRACTIONS.
Это значит, что для того, чтобы понять, что означает слово "модуль" в этом определении, нужно ориентироваться на эту статью, а не на "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 года - не такое, как в вашей статье.
И на этом изображении модуль Entities как-будто бы не состоит из сплошных абстракций... или, по вашему, состоит?
Состоит. И из того, что вынесение общих абстракций Мартин приводит следующим пунктом, альтернативным, я делаю вывод, что к моменту "Clean Architecture" (2017 год) его понимание DIP изменилось по сравнению с его же пониманием в 1996. Больше 20 лет прошло, это нормально...
Собственно, я потому и говорю, что объяснения в Clean Architecture - это реткон, потому что к этому моменту уже есть сложившееся использование (достаточно посмотреть в википедию), и надо быть очень акккуратным, чтобы не получить противоречий.
Какая именно зависимость инвертируется и почему именно она? Или название принципа не соответствует его содержанию?
Я уже цитировал Мартина по этому поводу:
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. Figure 1 is a good example of such a hierarchy. 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.
Из первой фразы очевидно, что это употребление казалось ему спорным прямо на момент написания этой статьи - но он его, тем не менее, сохранил.
Так что да, можно долго и упорно придумывать себе объяснение, что "это потому, что "нормальным образом" мы бы написали код, в котором копирование зависит от IO, а согласно этому принципу мы написали код, где IO зависит от абстракций"... или можем просто сказать, что слово "инверсия" там правда неудачное, и не надо на нем зацикливаться, а надо смотреть на то, какие проблемы и как решает этот принцип. Do as I do, not as I say.
Это буквально написано в приведенной вами же цитате: The policies that manage input and output are the lowest-level policies in the system. Т.е. input и output являются крайними в нашей рассматриваемой системе.
"Крайние" только с точки зрения уровня в этом определении. Это не значит, что они крайние в цепочке вызовов.
Даже базовый пример, который отображает работу программы, обычно изображают так: input - [программа] - output
Как из этого примера понять, где тут цепочка вызовов?
См. выше
Не понимаю. Что "выше" запрещает мне написать вот так:
void Translate()
{
var input = Console.ReadLine();
var translation = Dependency.Translate(input);
// ...
}
См. выше
Вот вам простой жизненный пример (я пишу для консоли, но в веб-приложении концептуально ничего не поменяется):
Исходный случай - это когда a() напрямую вызывает код в библиотеке. Самый простой пример (практически идентичный Мартиновскому) - это Console.ReadLine().
Да что там, вот код прямо из Мартиновской статьи. Вот это - исходный high level policy.
void Copy()
{
int c;
while ((c = ReadKeyboard()) != EOF)
WritePrinter(c);
}
(возможно, из Мартиновского кода вам не очевидно, что ReadKeyboard и WritePrinter менять нельзя, именно поэтому в моем примере Console.ReadLine(), который поставляется в стандартной библиотеке)
Нигде не встречал примеры, где абстракциям выделялось бы какое-то свое определенное место отдельное от рассматриваемых модулей/компонентов.
Да прямо в статье 1996 года и есть такой пример. Вы уверены, что вы слово "модуль" понимаете так же, как Мартин? Потому что в статье, о которой я говорю, нет модуля в вашем понимании этого слова (т.е. чего-то, внутри чего может быть интерфейс), там модуль - это подпрограмма (или класс, или абстрактный класс), там все элементы одного уровня.
А если открыть 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.
А дальше, в отдельноу главе, написано черным по белому:
In a statically typed language, like Java, this means that the use, import, and include statements should refer only to source modules containing interfaces, abstract classes, or some other kind of abstract declaration. Nothing concrete should be depended on.
Иными словами, "модуль", от которого зависят другие модули, может содержать только абстракции, но не реализации. Ваш МВУ это требование нарушает.
Проблема в том, что в реальной жизни модуль нижнего уровня - это third-party или системная библиотека. Самый простой пример - это, опять же, логгер. Или консоль.
В отношении "зависит от" нельзя смешивать объекты разной гранулярности. Модуль может зависеть только от модуля. А от интерфейса может зависеть только реализация.
Вообще, в природе есть такой зверь как "настольная лампа"...
Любопытно, кстати. Не знаете ли вы, случайно, где этот принцип был сформулирован (в таком виде) в первый раз? Потому что все упоминания, которые мне удалось найти - как раз в DIP.
Есть связанный, но отличающийся, принцип в GoF: "Program to an interface, not an implementation." Это 1994 год, это и просто по дате предшествует статье Мартина, и в его статье есть ссылки на GoF, так что он, очевидно, был в курсе этой работы; но это, все-таки, другой принцип.
На всякий случай добавлю, что да, необходимо, чтобы после введения абстракций ранее существовавшая зависимость модуля верхнего уровня от модуля нижнего уровня была разорвана, иначе абстракция действительно не несет пользы. Но не более того.
Гм.
Что такое, по вашему,
main
на этой диаграмме? Какому конкретно месту в коде традиционной программы оно соответствует? Что оно делает? Кто его вызывает?Нет, это именно просто абстракции. Это явно написано в двух приведенных цитатах (особенно, в первой, в книге 2006 года).
Нет, согласно двум приведенным мной цитатам это не обязательно.
Это меня не удивляет. Ведь иначе нельзя утверждать в заголовке, что 90% разработчиков чего-то не понимают.
Я повторюсь еще раз: вы утверждаете, что зависимости от абстракций, а не от реализаций, недостаточно для DIP. Цитаты из самого Мартина (кроме Clean Architecture, но вы не считаете, что она чему-то противоречит) говорят, что этого достаточно. Мой опыт тоже говорит, что достаточно. Так что, извините, я продолжу считать, что громкий заголовок вашей статьи не находит подтверждения в оригинальных работах.
Не обязательно есть, а может быть, и это не нарушает принципа (и даже разумно и оправдано, см. цитаты из википедии и самого Мартина).
Да нет же. Подпрограмма (или класс) - это не набор связанного функционала. Это просто подпрограмма.
Ну, если вы не видите, значит точно можно пользоваться ранним определением, в котором явно написано, что можно зависеть от абстракций. Это прямо противоречит вашей статье.
Если бы он не сомневался, он бы не писал "можно задаться вопросом". Риторические обороты - такие риторические.
Я явным образом написал, откуда он, даже определять не надо.
Принцип - не меняется. А то, как он применяется - меняется. Вы же не думаете, что у него ровно один вариант применения?
Это не является строгим требованием DIP (я уже приводил цитаты).
И именно потому, что это чрезвычайно избыточно, я и не рекомендую так делать.
Каждый раз, когда начинаю проект?.. Вы, наверное, не слышали про
Microsoft.Extensions.*
?Но зачем? Это не позволит переиспользовать интеграционный код, который зависит от этих абстракций, в других проектах, не связанных с этими "МВУ".
Вы действительно не видите намеков на cross-cutting concerns, даже когда я прямым текстом про логирование написал?
Не всегда.
Посмотрите на иллюстрацию 5.1 из той же главы:
main
- не высокоуровневая сущность. Более того, в консольной программеmain
может напрямую получить данные на вход, и дальше передать их в сущность более высокого уровня, та - в более высокий уровень и так далее. И это не только в консолях будет так: во многих современных программах entry point (не важно, будь это lambda function handler, или web controller, или event handler) - это элемент уровня представления, и он же - начало потока управления (по крайней мере, той чего части, которая контролируется и понятна разработчику).И в таких программах поток управления будет идти, грубо говоря, от какого-нибудь LLx, где сообщение пришло в систему, дальше в соответствующий MLx, который отвечает за обработку сценария, оттуда будет вызван HLx, который бизнес-сущности, а дальше в обратном порядке - сценарий (MLx) и обработчик ответа (LLy).
Я, прямо скажем, вообще не могу вспомнить ни одной системы, которая бы начинала свою работу с вызова доменного объекта (но я, понятное дело, работаю с определенным подмножеством систем).
Вот вам еще про этот же принцип, на этот раз из "Agile Principles, Patterns, and Practices in C#" (Micah Martin, Robert C. Martin, 2006) (к сожаление, у меня нет под рукой "Agile Software Development, Principles, Patterns, and Practices" (Robert C. Martin, Prentice Hall, 2002), но как можно будет дальше видеть, это не должно быть принципиально). В разделе 11, "The Dependency-Inversion Principle (DIP)" идет долгий (и почти дословный) пересказ кусков статьи 1996 года (именно поэтому я не думаю, что в книге 2002 есть фундаментальные отличия; забавно, что там опять есть объяснение "Over the years, many have questioned why I use the word inversion in the name of this principle"), после которого идет более интересный нам фрагмент "Ownership Inversion".
Это совпадает с тем, что говорите вы... но вот вам следующий абзац:
И это то, о чем говорю я - когда модули зависят не друг от друга, а от абстракций.
И, все оттуда же, буквально следующий абзац:
...из чего вытекает, что да, трактовка "зависьте от абстракций" была (в 2006 году; в 2017 из Clean Architecture можно сделать другие выводы, почему я и говорю, что важно оговаривать источники) для Мартина допустимым пониманием DIP. Что напрямую противоречит самому началу вашей статьи:
И, финальный аргумент в эту же сторону: в "Clean Architecture" Мартин ссылается на http://www.butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod:
Открываем эту ссылку, и читаем:
Выглядит так, что это все-таки допустимая трактовка, не правда ли?
То есть вы мне хотите сказать, что ни перед написанием своей статьи, ни за все время обсуждения вы не потрудились ознакомиться с тем источником, откуда пришло ваше определение?..
Можно.
Но вопрос в том, насколько специфичной будет эта обертка (и ее интерфейсы). Я же не зря в самом начале говорил про достаточно сложные системы: в изолированном примере у меня есть только политика Copy, но в системе у меня будет еще и политика Timestamp (которая к каждой строке из ввода добавляет время, и выплевывает в вывод), и политика Split, которая будет брать один ввод и делить его на два вывода. Каждая такая политика - отдельный модуль, и они ничего не знают друг о друге.
Где будут абстракции? Сколько адаптеров нужно написать?
Я, гм, говорю о задаче, которая приведена в фундаментальной статье про DIP как иллюстрация этого принципа.
Именно поэтому я стараюсь не усложнять системы, прикрываясь DIP, а делать их проще, не противореча ему.
"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."
("The Dependency Inversion Principle", Robert C. Martin, C++ Report, May 1996; раздел "Example: the “Copy” program.")
Я это говорю к тому, что в вашей статье приведено вот такое определение DIP:
Это определение дословно соответствует определению, данному в статье 1996 года:
Это значит, что для того, чтобы понять, что означает слово "модуль" в этом определении, нужно ориентироваться на эту статью, а не на "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 - это реткон, потому что к этому моменту уже есть сложившееся использование (достаточно посмотреть в википедию), и надо быть очень акккуратным, чтобы не получить противоречий.
Я уже цитировал Мартина по этому поводу:
Из первой фразы очевидно, что это употребление казалось ему спорным прямо на момент написания этой статьи - но он его, тем не менее, сохранил.
Так что да, можно долго и упорно придумывать себе объяснение, что "это потому, что "нормальным образом" мы бы написали код, в котором копирование зависит от IO, а согласно этому принципу мы написали код, где IO зависит от абстракций"... или можем просто сказать, что слово "инверсия" там правда неудачное, и не надо на нем зацикливаться, а надо смотреть на то, какие проблемы и как решает этот принцип. Do as I do, not as I say.
"Крайние" только с точки зрения уровня в этом определении. Это не значит, что они крайние в цепочке вызовов.
Как из этого примера понять, где тут цепочка вызовов?
Не понимаю. Что "выше" запрещает мне написать вот так:
Вот вам простой жизненный пример (я пишу для консоли, но в веб-приложении концептуально ничего не поменяется):
Этот пример выражает одновременно все, что я сказал.
Нет, вы неправильно понимаете.
Исходный случай - это когда
a()
напрямую вызывает код в библиотеке. Самый простой пример (практически идентичный Мартиновскому) - этоConsole.ReadLine()
.Да что там, вот код прямо из Мартиновской статьи. Вот это - исходный high level policy.
(возможно, из Мартиновского кода вам не очевидно, что
ReadKeyboard
иWritePrinter
менять нельзя, именно поэтому в моем примереConsole.ReadLine()
, который поставляется в стандартной библиотеке)Да, мой вопрос относился к предыдущей версии, которая звучала как "DIP тут не при чем".
Ну то есть у нас сначала библиотека была МНУ, а потом мы добавили еще один модуль, который стал новым МНУ, а библиотека, видимо, еще одним МНУ?
Мы хотим изолироваться не от библиотеки как таковой, а от конкретной реализации.
Да прямо в статье 1996 года и есть такой пример. Вы уверены, что вы слово "модуль" понимаете так же, как Мартин? Потому что в статье, о которой я говорю, нет модуля в вашем понимании этого слова (т.е. чего-то, внутри чего может быть интерфейс), там модуль - это подпрограмма (или класс, или абстрактный класс), там все элементы одного уровня.
А если открыть Clean Architecture, то там в определении нет слова модуль:
А дальше, в отдельноу главе, написано черным по белому:
Иными словами, "модуль", от которого зависят другие модули, может содержать только абстракции, но не реализации. Ваш МВУ это требование нарушает.
Но почему? Я прям прошел и перепроверил, пример Мартина оперирует чтением с клавиатуры и выводом на принтер. Это типичный пример системной библиотеки.
Проблема в том, что в реальной жизни модуль нижнего уровня - это third-party или системная библиотека. Самый простой пример - это, опять же, логгер. Или консоль.
Может, если абстракция - тоже модуль.
Тут же опять вопрос формулировок, и того, что конкретно вы называете модулем. Важно, чтобы гранулярность сохранялась.
Это не определение. И это в каком-то смысле реткон.
В отношении "зависит от" нельзя смешивать объекты разной гранулярности. Модуль может зависеть только от модуля. А от интерфейса может зависеть только реализация.