Найти в Дзене
Геннадий Шушпанов

Инверсия иллюзий

DIP --принцип инверсии зависимостей. Название призывает инвертировать зависимости, определение задает их направление -- сверху вниз, а типовые примеры показывают способ сокрытия реализации. На деле же, все кроме сокрытия является иллюзией. Определение оперирует зависимостью модулей. Но модуль лишь структурная единица. У него нет собственной функциональности. Когда мы говорим, что модуль A зависит от модуля B, то это означает лишь то, что объекты модуля A зависят от объектов из модуля B. Зависимость между модулями вторична и формальна. Рассмотрим типичный пример. У нас есть два модуля H и L, содержащие классы High и Low соответственно. При этом класс High использует поведение класса Low. Интерфейс класса состоит из опубликованных методов и свойств, а также из диапазонов значений свойств, параметров и результатов методов. Изменения, вносимые в класс, можно разделить на две категории: приводящие к изменению интерфейса класса или нет. Изменения первой категории могут оказать влияние на зав
Оглавление

DIP --принцип инверсии зависимостей. Название призывает инвертировать зависимости, определение задает их направление -- сверху вниз, а типовые примеры показывают способ сокрытия реализации. На деле же, все кроме сокрытия является иллюзией.

Иллюзия зависимости

Определение оперирует зависимостью модулей. Но модуль лишь структурная единица. У него нет собственной функциональности. Когда мы говорим, что модуль A зависит от модуля B, то это означает лишь то, что объекты модуля A зависят от объектов из модуля B. Зависимость между модулями вторична и формальна.

Иллюзия инверсии

Рассмотрим типичный пример. У нас есть два модуля H и L, содержащие классы High и Low соответственно. При этом класс High использует поведение класса Low. Интерфейс класса состоит из опубликованных методов и свойств, а также из диапазонов значений свойств, параметров и результатов методов. Изменения, вносимые в класс, можно разделить на две категории: приводящие к изменению интерфейса класса или нет. Изменения первой категории могут оказать влияние на зависимые классы, а изменения второй категории -- нет.

В нашем примере внесение изменений второй категории в методы A или B класса Low не приведет к изменениям в классе High. А вот добавление параметров или изменение политики возбуждение исключений потребует таких изменений.

Последуем рекомендациям. Добавим в модуль H интерфейс ILow и перепишем наши классы с его учетом. Теперь, формально, у нас модуль L зависит от H. А на деле все осталось как и было. При изменениях классы ведут себя точно также. Добавив параметр в метод A мы будем вынуждены изменить метод A в интерфейсе ILow, что спровоцирует изменения в классе High.

-2

Иллюзия абстракции

Использование абстракции не позволяет нам уйти от зависимости при изменении используемого класса. Абстракции не возникают из ничего. Они продукт исходной зависимости. Классу High нужно от класса Low поведение реализуемое в методах A и B. И эта зависимость никуда не денется. Она трансформируется из явной при использовании класса в неявную при использовании интерфейса.

Иллюзия цели

На вопрос "зачем инвертировать зависимости?" Google отвечает "для минимизации зацепления в компьютерных программах". У нас была одна зависимость: High зависел от Low. Она никуда не делась, но появились зависимости High и Low от ILow. А где-то еще появился некий класс Factory, зависящий от High и Low. И, потенциально, еще некоторое количество классов реализующих ILow, от которых High тоже начнет зависеть по мере их появления. И кто вспомнит, внося изменения в SomeLow, что где-то есть еще не один xxLow реализующий этот же интерфейс.

Диалектика

Плюсы растут из минусов. Интерфейс может покрывать множество классов и благодаря этому мы получаем полезные возможности.

Например при разработке программы обработки заказов, хранящихся в базе данных можно вместо полноценного класса работающего с базой данных, написать легкий класс на списке. Это позволит выполнять задачи параллельно и облегчит процесс формирования тестовых данных.

В качестве другого примера можно привести сценарий "Поставщик, обработчик, хранилище", в котором, например, информация о заказе должна быть получена от поставщика, обработана и сохранена в хранилище. Использование интерфейса позволит всем трем акторам использовать свою реализацию для класса заказа.

В качестве третьего примера приведу ситуацию использования кода в разных проектах. Реализация Low для нового проекта может не подходить. Low сам может зависеть от классов, которые не нужны или не походят для нового проекта. В этом случае переход к интерфейсу разрешит проблему. Ну а если вы его сразу использовали, то она и е возникнет.

-3