Пусть что у нас есть два программных модуля, которые взаимодействуют между собой. Один модуль вызывает другой, и наоборот. Для масштаба представьте, что один модуль это Microsoft Word, а другой - Microsoft Windows. Вы нажали клавишу - драйвер клавиатуры формирует событие, Windows передаёт его в Word, тот вставляет букву в файл и шлёт запрос к Windows, чтобы нарисовать её на экране.
Софт такого уровня обычно разрабатывают на языках со статической типизацией, скажем С++. Что означает взаимодействие модулей для программиста? Это означает, что в исходный код вызывающего модуля мы включаем include-файл от вызываемого модуля. В нём определены функции и структуры данных, нужные нам для обращения к этому модулю. Если бы все программы строились по этому принципу, это был бы кошмар. Любое изменение в структурах данных Windows, даже простейшее, приводило бы к необходимости пересобрать не только Windows, но и Word, и тысячи других программ. Мало того, изменения в Word или браузере, или игрушках вызывали бы перекомпиляцию самого Windows. К счастью, этого не происходит, потому как архитекторы давно додумались до принципа инверсии зависимостей (Dependency inversion principle).
Типичный случай двух взаимодействующих модулей. Каждый модуль вызывает другой, а значит, зависит от его исходников.

Правильное решение проблемы - выделение межмодульных интерфейсов в отдельные компоненты (абстрактные классы в Си++). Интерфейсы не содержат деталей реализации, и поэтому слабо подвержены изменениям. Вот такая ситуёвина получается:

Пример из жизни: интерфейс системных вызовов ядра Линукса крайне редко меняется. Поэтому при обновлении ядра все приложения продолжают работать.
Я сбацал пример, фактически шаблон для разделения интерфейсов взаимодействующих классов в Си++. Посмотреть можно на Гитхабе: https://github.com/sergev/dependency-inversion-demo
Софт такого уровня обычно разрабатывают на языках со статической типизацией, скажем С++. Что означает взаимодействие модулей для программиста? Это означает, что в исходный код вызывающего модуля мы включаем include-файл от вызываемого модуля. В нём определены функции и структуры данных, нужные нам для обращения к этому модулю. Если бы все программы строились по этому принципу, это был бы кошмар. Любое изменение в структурах данных Windows, даже простейшее, приводило бы к необходимости пересобрать не только Windows, но и Word, и тысячи других программ. Мало того, изменения в Word или браузере, или игрушках вызывали бы перекомпиляцию самого Windows. К счастью, этого не происходит, потому как архитекторы давно додумались до принципа инверсии зависимостей (Dependency inversion principle).
Типичный случай двух взаимодействующих модулей. Каждый модуль вызывает другой, а значит, зависит от его исходников.

Правильное решение проблемы - выделение межмодульных интерфейсов в отдельные компоненты (абстрактные классы в Си++). Интерфейсы не содержат деталей реализации, и поэтому слабо подвержены изменениям. Вот такая ситуёвина получается:

Пример из жизни: интерфейс системных вызовов ядра Линукса крайне редко меняется. Поэтому при обновлении ядра все приложения продолжают работать.
Я сбацал пример, фактически шаблон для разделения интерфейсов взаимодействующих классов в Си++. Посмотреть можно на Гитхабе: https://github.com/sergev/dependency-inversion-demo

no subject
Date: 2021-01-06 16:07 (UTC)Ну или CORBA, для тех кто не любит MS
no subject
Date: 2021-01-06 17:12 (UTC)DLLs и подобные технологии решают эту проблему (за счёт того, что линк делается только на то что действительно нужно). Но в этот момент от C++ и headers там остались только рожки да ножки. Отлаживать приходится в бинарниках.
Если нужно решать эти проблемы на уровне c++, компилировать придётся при install (или чаще).