- •3 Системное программное обеспечение.
- •Инструментарий технологии программирования.
- •Пакеты прикладных программ.
- •Суть поиска подходящих объектов при проектировании.
- •Специфицирование интерфейсов объекта.
- •Специфицирование реализации объектов.
- •Механизмы повторного использования.
- •Сравнение структур времени выполнения и времени компиляции.
- •Проектирование с учетом будущих изменений
- •Прикладные программы. Инструментальные библиотеки. Каркасы приложений.
- •Признаки плохого проекта.
- •Принцип персональной ответственности
- •15. Принцип открытия – закрытия (оср)
- •17. Принцип инверсии зависимостей.
- •Обратное влияние клиентов на интерфейсы
17. Принцип инверсии зависимостей.
Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба типа модулей обязаны зависеть от абстракций. Абстракции не должны зависеть от подробностей. Подробностям следует зависеть от абстракций.
Рассмотрим зависимость модулей высокого уровня от низкоуровневых модулей. Именно модули высокого уровня включают важные политические решения и бизнес-модели приложения. Они описывают суть приложения. Но до тех пор, пока эти модули зависят от модулей низкого уровня, изменения последних могут непосредственно влиять на функционирование модулей более высокого уровня. В итоге формируется пресловутый "замкнутый круг"! Существуют модули высокого уровня, устанавливающие определенные политики, но на них влияют в целом детали модули низкого уровня. Модули, содержащие описания бизнес-правил высокого уровня, должны иметь определенную независимость и приоритет перед модулями, включающими детали. Модули высокого уровня ни в коей мере не должны зависеть от модулей низкого уровня. Более того, к высокоуровневым модулям, определяющим основные положения политик, приходится обращаться повторно. Модули низкого уровня достаточно удобно использовать повторно в виде библиотек подпрограмм. Если модули высокого уровня зависят от модулей низкого уровня, довольно сложно использовать высокоуровневые модули в различных контекстах. Но если модули высокого уровня не зависят от модулей низкого уровня, высокоуровневые модули легко применять повторно. Этот принцип положен в основу схематического проектирования (framework design).
Слой Policy |
|
|
|
|
|
|
|||
|
|
Слой Mechanism |
|
|
|
|
V |
||
|
|
|
Слой Utility |
На этой диаграмме слой высокого уровня Policy использует слой низкого уровня Mechanism, который в свою очередь задействует слой на уровне деталей Utility. С сожалением приходится констатировать, что слой Policy сильно реагирует на любые изменения слоя Utility. Свойство зависимости обладает транзитивностью. Слой Policy зависит от некоторых понятий, относящихся к слою Utility; поэтому слой Policy транзитивно зависит от слоя Utility. На рис..2 показана более совершенная модель. Каждый слой верхнего уровня объявляет абстрактный интерфейс для необходимых служб. Затем на основе этих абстрактных интерфейсов реализуются слои нижних уровней. Каждый класс более высокого уровня с помощью абстрактного интерфейса использует следующий нижайший уровень. Таким образом, слои верхнего уровня не зависят от слоев низкого уровня. Вместо этого слои, расположенные ниже, зависят от абстрактных служебных интерфейсов, объявленных в верхних слоях. Нарушается не только транзитивная зависимость PolicyLayer от UtilityLayer, но также и непосредственная зависимость PolicyLayer от MechanismLayer. Policy
|
|
«interface» Policy Service Interface - |
Слой Policy |
|
|
|
||
|
|
Mechanism |
|
|
|
|
||
|
|
|
|
|
||
|
«interface» Mechanism Service Interface |
|
||||
Слой Mechanism |
||||||
|
||||||
|
||||||
|
|
|
й |
|
||
|
| |
|||||
Utility |
i |
|||||
|
1 |
|
||||
|
Слой Utility |
|
||||
|
|
|
|
Обратите внимание, что инверсия затрагивает не только отношения зависимости, но также и отношения собственности для интерфейсов. Можно представлять, что библиотеки утилит содержат собственные интерфейсы. Применяя принцип DIP, заметим, что клиенты располагают абстрактными интерфейсами, а серверы просто их используют. Используя инверсию собственности, PolicyLayer не затрагивается изменениями на слоях MechanismLayer или UtilityLayer. Более того, PolicyLayer может повторно применяться в любом контексте, определяющем модули нижних уровней в соответствии с PolicyServicelnterface. Таким образом, при инвертировании зависимостей получаем более гибкую, надежную и переносимую структуру. В упрощенной форме суть принципа DIP можно интерпретировать с помощью эвристического утверждения о "зависимости от абстракций". Проще говоря, рекомендуется не использовать зависимость от статичного класса — все взаимоотношения в программе поддерживаются с помощью абстрактного класса или интерфейса. В соответствии с этим утверждением получим ряд выводов: -ни одна переменная не должна содержать указатель или ссылку на статичный класс; -ни один класс не должен порождаться статичным классом. -ни один метод не должен отвергать метода реализации любого из базовых классов.
Инверсия зависимостей проявляется всякий раз, когда один класс направляет сообщение другому. Например, рассмотрим взаимоотношения объектов Button и Lamp. Объект Button воспринимает воздействие со стороны внешней среды. После получения сообщения Poll этот объект определяет, "нажал" ли соответствующую кнопку пользователь. Не имеет значения механизм связи с внешней средой. Пиктограмма кнопки может относиться к графическому интерфейсу пользователя, воздействие на реальную кнопку осуществляется путем нажима пользователем, а движение объекта, связанного с кнопкой, — отслеживаться домашней системой безопасности. Объект Button уточняет, активизировал ли пользователь в данный момент кнопку.
Button |
|
|
+ TurnOn() + TurnOff() |
||
+ PolK) |
|
На объект Lamp оказывает влияние внешняя среда. После получения сообщения TurnOn зажигается свет. При получении сообщения Turnof f — свет гаснет. При этом не важен физический механизм произведенных действий. Каким образом сконструировать систему, в которой объект Button управляет объектом Lamp? На рис. приводится соответствующая примитивная схема. Объект Button получает сообщения Poll, уточняет, не нажата ли кнопка, а затем просто пересылает объекту Lamp сообщение TurnOn или Turnof. В чем же состоит примитивизм подобной схемы? Обратимся к коду Java, использующему эту модель. Обратите внимание, что класс Button непосредственно зависит от класса Lamp. Вследствие этой зависимости на класс Button оказывают влияние изменения в классе Lamp. Более того, отсутствует возможность повторного использования Button для контроля над объектом Motor. При данной схеме разработки объекты Button контролируют объекты Lamp и только объекты Lamp.
public class Button { private Lamp itsLamp; public void poll() {
if (/*условие*/) itsLamp.turnOn();} Приведенное решение нарушает основополагающие условия, накладываемые принципом DIP. Высокоуровневая политика приложения не отделена от реализации низкого уровня. Абстракции не отделены от деталей. Без подобного отделения политика высокого уровня автоматически попадает в зависимость от модулей низких уровней, а абстракции — от деталей.
18.Принцип отделения интерфейсов.
Этот принцип позволяет преодолевать недостатки, связанные с чрезмерной "толщиной" интерфейсов. Классы, имеющие "тучные" интерфейсы, недостаточно компактны. Эти интерфейсы можно разбить на группы методов, и каждая группа обслуживает иной набор клиентов. Поэтому каждая группа клиентов использует определенную группу членов-функций. Принцип ISP ориентируется на наличие объектов, нуждающихся в несвязанных интерфейсах; также учитывается, что клиенты не должны представлять их как отдельный класс. Вместо этого клиентам следует иметь представление об абстрактных базовых классах, имеющих связанные интерфейсы. Рассмотрим систему обеспечения безопасности. В этой системе имеются объекты Door, которые могут быть как блокированы, так и открыты. Security Door class Door
{public: virtual void Lock() = 0; virtual void Unlock() = 0; virtual bool IsDoorOpent) = 0;};
Данный класс абстрактен, поэтому клиенты могут применять объекты, относящиеся к интерфейсу Door, вне зависимости от определенных реализация Door. Рассмотрим одну из таких реализаций, TimedDoor, которая подает звуковой сигнал, если дверь открыта слишком долго. Чтобы выполнить эти действия, объект TimedDoor связывается с другим объектом под названием Timer.
class Timer {public:
void Register(int timeout, TimerClient* clients);};
class TimerClient
{ public: virtual void TimeOut() = 0;};
Если объект желает проинформировать о перерыве, вызывается функция Register для Timer. Аргументами этой функции являются время перерыва и указатель объекта TimerClient, чья функция TimeOut и вызывается по завершении перерыва. Каким образом связать класс TimerClient с классом TimedDoor, чтобы код в TimedDoor уведомлял о наступившем перерыве? Существуют несколько альтернатив. На рис. проиллюстрировано примитивное решение. Структура сформирована таким образом, что классы Door и TimedDoor наследуются из TimerClient. Получаем гарантию, что TimerClient сможет зарегистрироваться с помощью Timer и получить сообщение TimeOut.