Мои контакты


вторник, 19 июля 2011 г.

Принцип устойчивых зависимостей (Stable Dependencies Principle)

Всем привет!
Сегодня я расскажу о принципе устойчивых зависимостей, знание которого просто необходимо для грамотного проектирования и распределения ответственностей и зависимостей между компонентами системы.

Дизайн разрабатываемой системы не может постоянно оставаться неизменным. Особенно, если систему приходится сопровождать, то она будет довольно часто подвергаться изменениям. Любая система состоит из компонентов, каждый из компонентов имеет разные связи с другими компонентами. Какие-то части подвергаются частым изменениям, а какие-то гораздо реже (скорее всего, это архитектурные решения). В соответствии с частотой изменений, мы должны грамотно уметь проектировать модули. Компоненты, которые, как мы предполагаем, будут редко изменяться, не должны зависеть от компонентов, которые будут меняться часто.

Формулировка принципа SDP:
Зависимости должны быть направлены в сторону устойчивости.

Командная разработка – это дело особенное, каждый вносит свою лепту в проект. Бывает, например, так, что вы разработали модуль, который достаточно легко подвергается изменениям, а какой-то другой разработчик из команды может его легко сделать трудноизменяемым, просто понавесив на ваш модуль лишних зависимостей. По сути, SDP говорит, что от модулей, которые изначально спроектированы так, чтобы их легко можно было изменять, не должны зависеть модули, изменение которых затруднено. Это так называемые устойчивые модули.

Поговорим об устойчивости. Как можно объяснить устойчивость? Вот, например, рояль является устойчивым? А человек, стоящий на одной ноге? Мне кажется, что человека, стоящего на одной ноге, гораздо легче опрокинуть, чем рояль. То есть для этого нужно приложить гораздо меньше усилий. Поэтому можно сказать, что человек, стоящий на одной ноге – не является устойчивым, а рояль – устойчив. Таким образом, устойчивость – это что-то вроде меры силы, необходимой для выведения из положения равновесия.

Какой компонент системы можно назвать устойчивым? Компонент, в сторону которого направлено много зависимостей, т.к. любое изменение этого компонента требует работы по увязке этих изменений в зависимых компонентах. Рассмотрим примеры.

stableComponent

Компонент А является устойчивым, т.к. от него зависят три других компонента, он несет за них ответственность. У этого компонента есть как минимум три причины его не изменять. Также А является независимым, потому что для его изменения нет внешних стимулов.

unstableComponent

А на этом рисунке обратный пример. Компонент B очень не устойчив, т.к. существует как минимум три внешних причины для его изменения. От этого компонента ничего не зависит – это компонент неответственный.

Чтобы делать конкретные оценки по поводу устойчивости компонентов, существуют метрики устойчивости, которыми пользуются специализированные утилиты, такие как NCover. Формула для вычисления метрики устойчивости одного модуля:

CodeCogsEqn (1)

Здесь Mo - это число классов вне данного модуля M, зависящих от классов внутри модуля M, а Mi – это число классов внутри данного модуля, зависящих от классов вне модуля M. Значение S(M) изменяется от [0; 1], при этом 0 означает абсолютную неустойчивость модуля, т.е. не существует классов, которые бы зависели от классов внутри модуля, а 1 означает абсолютную устойчивость.

Мы узнали, как посчитать метрику устойчивости для компонента и это поможет нам понять удовлетворяет ли набор компонентов принципу SDP. Зависимости должны быть направлены в сторону устойчивости, поэтому метрика устойчивости компонента должна уменьшаться с каждым новым уровнем зависимости. Проще говоря, метрика устойчивости S(M) для какого-либо компонента должна быть меньше, чем метрика устойчивости компонентов, от которых он зависит. Рассмотрим схему компонентов:

idealScheme

Если вся система состоит из этих трех компонентов, то это идеальная схема. Наверху расположены два неустойчивых компонента: S(A) = S(B) = 0. От них ничего не зависит, это, скорее всего, какие-то конкретные абстрактных классов, и сами они зависят от компонента C. В свою очередь компонент C является очень устойчивым (S(C) = 1), это ответственный компонент.

Нарушить эту идеальную схему достаточно просто – можно сделать C, зависимым от какого-нибудь компонента D, который в свою очередь тоже является зависимым от других (возможно, устойчивых) модулей. Такое часто происходит при командной разработке.

Заключение. Практически все базовые архитектурные решения (фундамент системы) стоит размещать в устойчивых компонентах (S = 1), а части системы, которые предполагается часто изменять, лучше укладывать в неустойчивые компоненты. Но это не всегда работает. Ведь если высокоуровневый дизайн поместить в устойчивые компоненты, то код будет трудно изменять, получается, что дизайн становится не гибким.  Как сделать, чтобы максимально устойчивый компонент оставался гибким? Для этого нужно использовать классы, которые можно расширять без модификации, т.е. абстрактные (см. принцип открытости/закрытости). Мы плавно подошли к принципу устойчивых абстракций. Но это в следующей статье.

Ссылки. Если вы еще не знакомы, то обязательны для изучения принципы SOLID:
Single Responsibility Principle;
Open/Closed Principle;
Liskov Substitution Principle;
Interface Segregation Principle;
Dependency Inversion Principle.