Если все компоненты в системе будут иметь максимальную устойчивость, такую систему невозможно будет изменить. Это нежелательная ситуация. В действительности структура компонентов должна проектироваться так, чтобы в ней имелись и устойчивые, и неустойчивые компоненты. Диаграмма на рис. 14.8 демонстрирует идеальную организацию системы с тремя компонентами.
Изменяемые компоненты находятся вверху и зависят от устойчивого компонента внизу. Размещение неустойчивых компонентов в верхней части диаграммы — общепринятое и очень удобное соглашение, потому что любые стрелки, направленные
Рис. 14.8. Идеальная организация системы с тремя компонентами
Диаграмма на рис. 14.9 демонстрирует нарушение принципа SDP.
Компонент Flexible специально проектировался так, чтобы его было легко изменять. Предполагалось, что он будет неустойчивым. Но кто-то из разработчиков, работающих над компонентом Stable, создал зависимость от компонента Flexible. Это явное нарушение принципа SDP, потому что
Рис. 14.9. Нарушение принципа SDP
метрика
Чтобы исправить проблему, нужно разорвать зависимость 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 очень устойчив (
Рис. 14.11. Класс C реализует интерфейс US
Абстрактные компоненты
Кому-то может показаться странным, что мы создали компонент — в данном примере UService, — не содержащий ничего, кроме интерфейса. То есть компонент не содержит выполняемого кода! Однако, как оказывается, это весьма распространенная и единственно возможная тактика в языках со статической системой типов, таких как Java и C#. Такие абстрактные компоненты очень устойчивы и поэтому служат идеальной целью для зависимостей в менее устойчивых компонентах.
В языках с динамической системой типов, таких как Ruby или Python, подобные абстрактные компоненты вообще отсутствуют, так же как зависимости, которые можно было бы нацелить на них. Структура зависимостей в этих языках намного проще, потому что для инверсии зависимостей не требуется объявлять или наследовать интерфейсы.
Принцип устойчивости абстракций
Устойчивость компонента пропорциональна его абстрактности.
Куда поместить высокоуровневые правила?
Некоторые части программных систем должны меняться очень редко. Эти части представляют высокоуровневые архитектурные и другие важные решения. Никто не желает, чтобы такие решения были изменчивыми. Поэтому программное обеспечение, инкапсулирующее высокоуровневые правила, должно находиться в устойчивых компонентах (
Но если высокоуровневые правила поместить в устойчивые компоненты, это усложнит изменение исходного кода, реализующего их. Это может сделать всю архитектуру негибкой. Как компонент с максимальной устойчивостью (
Введение в принцип устойчивости абстракций
Принцип устойчивости абстракций (Stable Abstractions Principle; SAP) устанавливает связь между устойчивостью и абстрактностью. С одной стороны, он говорит, что устойчивый компонент также должен быть абстрактным, чтобы его устойчивость не препятствовала расширению, с другой — он говорит, что неустойчивый компонент должен быть конкретным, потому что неустойчивость позволяет легко изменять его код.