Напротив, когда метрика I равна 0, это означает, что от компонента зависят другие компоненты ( Fan-in > 0), но сам он не зависит от других компонентов ( Fan-out = 0). Такой компонент ответствен и независим . Он занимает максимально устойчивое положение. Зависимости от него усложняют изменение компонента, а отсутствие компонентов, от которых он зависит, означает отсутствие сил, которые могли бы заставить его измениться.
Принцип устойчивых зависимостей (SDP) говорит, что метрика I компонента должна быть больше метрик I компонентов, которые от него зависят. То есть метрики I должны уменьшаться в направлении зависимости.
Не все компоненты должны быть устойчивыми
Если все компоненты в системе будут иметь максимальную устойчивость, такую систему невозможно будет изменить. Это нежелательная ситуация. В действительности структура компонентов должна проектироваться так, чтобы в ней имелись и устойчивые, и неустойчивые компоненты. Диаграмма на рис. 14.8 демонстрирует идеальную организацию системы с тремя компонентами.
Изменяемые компоненты находятся вверху и зависят от устойчивого компонента внизу. Размещение неустойчивых компонентов в верхней части диаграммы — общепринятое и очень удобное соглашение, потому что любые стрелки, направленные вверх , ясно покажут нарушение принципа устойчивых зависимостей (и, как вы убедитесь далее, принципа ацикличности зависимостей).
Рис. 14.8.Идеальная организация системы с тремя компонентами
Диаграмма на рис. 14.9 демонстрирует нарушение принципа SDP.
Компонент Flexible специально проектировался так, чтобы его было легко изменять. Предполагалось, что он будет неустойчивым. Но кто-то из разработчиков, работающих над компонентом Stable, создал зависимость от компонента Flexible. Это явное нарушение принципа SDP, потому что
Рис. 14.9.Нарушение принципа SDP
метрика I компонента Stable намного меньше метрики I компонента Flexible. Как результат, создание такой зависимости усложнило возможное изменение компонента Flexible. Теперь любые изменения в компоненте Flexible придется согласовывать с компонентом Stable и всеми компонентами, зависящими от него.
Чтобы исправить проблему, нужно разорвать зависимость Stable от Flexible. Зачем нужна эта зависимость? Допустим, что в компоненте Flexible имеется класс C, который используется другим классом U из компонента Stable (рис. 14.10).
Рис. 14.10.Класс U в компоненте Stable использует класс C в компоненте Flexible
Исправить ситуацию можно, применив принцип инверсии зависимостей (DIP). Для этого определим интерфейс US и поместим его в компонент с именем UServer. Этот интерфейс должен объявлять все методы, используемые классом U. Затем реализуем этот интерфейс в классе C, как показано на рис. 14.11. Это разорвет зависимость Stable от Flexible и вынудит оба компонента зависеть от UServer. UServer очень устойчив ( I = 0), а Flexible сохранит желаемую неустойчивость ( I = 1). Теперь все зависимости простираются в сторону уменьшения I .
Рис. 14.11.Класс C реализует интерфейс US
Кому-то может показаться странным, что мы создали компонент — в данном примере UService, — не содержащий ничего, кроме интерфейса. То есть компонент не содержит выполняемого кода! Однако, как оказывается, это весьма распространенная и единственно возможная тактика в языках со статической системой типов, таких как Java и C#. Такие абстрактные компоненты очень устойчивы и поэтому служат идеальной целью для зависимостей в менее устойчивых компонентах.
В языках с динамической системой типов, таких как Ruby или Python, подобные абстрактные компоненты вообще отсутствуют, так же как зависимости, которые можно было бы нацелить на них. Структура зависимостей в этих языках намного проще, потому что для инверсии зависимостей не требуется объявлять или наследовать интерфейсы.
Читать дальше
Конец ознакомительного отрывка
Купить книгу