Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

2022_031

.pdf
Скачиваний:
1
Добавлен:
01.01.2024
Размер:
1.16 Mб
Скачать

Таблица 1. Различия между двумя графическими программами

 

Методы в DP1

Методы в DP2

 

 

 

Метод отображения

draw_a_line(x1, y1, x2, y2)

drawline (x1, x2, y1, y2)

линий

 

 

 

 

 

Метод отображения

draw_a_circle(x, y, r)

drawcircle(x, y, r)

окружностей

 

 

 

 

 

Аргументы в методе программы DP2 отличаются и должны быть переупорядочены.

Заказчик поставил условие, что клиент, использующий программу отображения прямоугольников, не должен знать, какая именно графическая программа будет использоваться в каждом случае (символ видимости # - защищенный - перед описанием метода drawLine()). Таким образом, при инициализации прямоугольника, какая именно из графических программ должна использоваться, определяется системой. Тогда можно создать два различных типа объекта прямоугольника. Один из них будет вызывать для отображения программу DP1, а другой — программу DP2. В каждом типе объекта будет присутствовать метод отображения прямоугольника, но реализованы они будут по-разному — как показано на рисунке 9.

Создав абстрактный класс Rectangle (прямоугольник), можно воспользоваться тем преимуществом, что единственное несовпадение между различными типами его производных классов состоит в способе реализации метода drawLine(). В классе V1Rectangle он реализован посредством ссылки на объект DP1 и его метод draw_a_line(), а в классе V2Rectangle — посредством ссылки на объект DP2 и его метод drawline().

Предположим, стало необходимо включить в программу поддержку еще одного вида геометрических фигур — окружностей. При этом клиент не должен ничего знать о различиях, существующих между объектами, представляющими прямоугольники (Rectangle) и окружности (Circle).

Логично будет сделать вывод, что можно применить выбранный ранее подход и просто добавить еще один уровень в иерархию классов программы. Потребуется только добавить в проект новый абстрактный класс (назовем его Shape (фигура)), а классы Rectangle и Circle будут производными от него. В этом случае объект Client сможет просто обращаться к объекту класса Shape, совершенно не заботясь о том, какая именно геометрическая фигура им представлена.

Для аналитика, только начинающего работать в области объектноориентированной разработки, такое решение может показаться вполне

21

естественным — реализовать изменение в требованиях только с помощью механизма наследования. Начав со схемы, представленной на рисунке 9, добавим к ней новый уровень с классом Shape.

Рисунок 9 - Схема программы отображения прямоугольников с помощью программ DP1 и DP2

Затем для всех видов фигур организуем их реализацию с помощью каждой из графических программ, создав по отдельному производному классу для вызова объектов DP1 и DP2, как в классе Rectangle, так и в классе Circle. В итоге будет получена схема, подобная представленной на рисунке 10.

Класс Circle можно реализовать тем же способом, что и класс Rectangle — как показано на рисунке 9. Единственное отличие состоит в том, что вместо метода drawLine() используется метод drawCircle().

Если проанализируем, как работает метод draw() класса V1Rectangle, построив диаграмму последовательностей обращения к объекту класса

V1Rectangle, то увидим:

Метод draw() класса Rectangle четыре раза подряд вызывается метод drawLine().

Метод drawLine() реализуется посредством обращения к методу draw_a_line() объекта DP1.

Ксожалению, этот подход создает новые проблемы. На рисунке 10 видно, что третий ряд классов - это те четыре конкретных подтипа класса Shape, с которыми мы имеем дело. Если появится еще один возможный вариант реализации графических функций, то потребуется создать шесть различных подтипов класса Shape (для представления двух видов фигур и трех графических программ).

22

Рисунок 10 - Простейший прямолинейный подход к реализации двух фигур с помощью двух графических программ

Если к тому же потребуется реализовать поддержку нового, третьего, типа фигур, то понадобятся уже девять различных подтипов класса Shape (для представления трех видов фигур и трех графических программ).

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

Шаблон Bridge обеспечивает подобную независимость изменений. Отметим, в объектно-ориентированном проектировании часто разра-

ботчик, анализируя описание проблемной области, выделяет в нем существительные и создает объекты, представляющие их. Затем он отыскивает глаголы, связанные с этими существительным (т.е. их действия), и реализует их, добавляя к объектам необходимые методы. Подобный процесс в большинстве случаев приводит к созданию слишком громоздкой иерархии классов.

23

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

Применим такой подход к нашей задаче с прямоугольниками и окружностями. Сначала идентифицируем то, что изменяется. В нашем случае это различные типы фигур (представленных абстрактным классом Shape) и различные версии графических программ. Следовательно, общими концепциями являются понятия типа фигуры и графической программы, как показано на рисунке 11 (Обратите внимание на то, что имена классов выделены курсивом, поскольку это абстрактные классы.)

Рисунок 11 - Сущности, которые изменяются в нашем примере

В данном случае предполагается, что абстрактный класс Shape инкапсулирует концепцию типов фигур, с которыми необходимо работать. Каждый тип фигуры должен знать, как себя нарисовать. В свою очередь, абстрактный класс Drawing (рисование) отвечает за вычерчивание линий и окружностей. На рисунке указанные выше обязательства представлены посредством определения соответствующих методов в каждом из классов.

Теперь необходимо представить на схеме те конкретные вариации, с которыми мы будем иметь дело. Для класса Shape это прямоугольники (класс Rectangle) и окружности (класс Circle). Для класса Drawing это графические программы DP1 (класс V1Drawing) и DP2 (класс V2Drawing). Все это схематически показано на рисунке 12.

Для описания способа взаимодействия этих классов попытаемся обойтись без того, чтобы добавлять в систему еще один новый набор классов, построенный на углублении иерархии наследования, поскольку последствия этого нам уже известны ( рисунок 10). На этот раз мы подойдем с другой стороны и попробуем определить, как эти классы могут использо-

24

вать друг друга. Главный вопрос здесь состоит в том, какой же из абстрактных классов будет использовать другой?

Рисунок 12 - Представление конкретных вариаций

Если допустить, что класс Drawing использует классы фигур, т.е. класс Shape. Тогда объекты класса Drawing должны были бы знать определенную информацию об объектах класса Shape, чтобы иметь возможность отобразить их (а именно — тип конкретной фигуры). Однако это требование нарушает фундаментальный принцип объектной технологии: каждый объект должен нести ответственность только за себя.

Тогда логичнее предположить, что класс Shape использует класс Drawing, т.е. объекты класса Shape для отображения себя будут использовать объекты класса Drawing. Действительно, объектам класса Shape не нужно знать, какой именно тип объекта класса Drawingбудет использоваться, поэтому классу Shapeможно разрешить ссылаться на класс Drawing. Дополнительно класс Shape в этом случае можно сделать ответственным за управление рисованием. Графически это решение представлено на рисунке

13.

В данном случае класс Shape использует класс Drawing для проявления собственного поведения. Мы оставляем без внимания особенности реализации классов V1Drawing, использующего программу DP1, и V2Drawing, использующего программу DP2. На рисунке 13 эта задача решена посредством добавления в класс Shape защищенных методов drawLine() и drawCircle(), которые вызывают методы drawLine() и drawCircle() объекта Drawing, соответственно.

25

Рисунок 13 - Связывание абстрактных классов между собой

Важный принцип

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

В методе draw() класса Rectangle можно было бы непосредственно вызвать метод DrawLine() того объекта класса Drawing, к которому обращается объект класса Shape. Однако, следуя указанной выше стратегии, можно улучшить программный код, создав в классе Shape метод drawLine(), который и будет вызывать метод DrawLine() класса Drawing, тем самым мы готовим фундамент для появления других производных классов фигур, при вычерчивании которых могут потребоваться линии и окружности.

На рисунке 14 показано, как абстракция Shape может быть отделена от реализации Drawing

С точки зрения методов, новый вариант системы весьма похож на реализацию, построенную на наследовании (рисунок 10). Главное отличие состоит в том, что здесь методы расположены в различных объектах.

26

Шаблон Bridge позволяет взглянуть на реализацию как на нечто, находящееся вне наших объектов, нечто используемое этими объектами. В результате мы получаем большую свободу за счет сокрытия вариации в реализации от вызывающей части программы. Иерархия, изображенная на рисунке 14 слева, включает вариации в абстракциях. Иерархия справа включает вариации в том, как будут реализованы эти абстракции. Такой подход отвечает новой парадигме создания объектов (использование анализа общности и изменчивости), упомянутой выше.

.

Рисунок 14 - Диаграмма классов, иллюстрирующая отделение абстракции от реализации

2.2. Порядок выполнения работы

1.Изучить теоретическую часть по приведенным выше данным.

2.Ответить на контрольные вопросы.

3.В модели, разработанной в лабораторной работе 1, найти возможность применения паттернов Facade, Adapter, Bridge.

4.Представить отчет, который должен содержать:

постановку задачи;

диаграммы используемых паттернов,

описание представленных диаграмм.

2.3.Контрольные вопросы

1.Почему нужно строить разные диаграммы при моделировании системы?

27

2.Какие диаграммы соответствуют статическому представлению о системе?

3.Вы разрабатываете компьютерную программу для игры в шахматы. Какая диаграмма UML была бы полезной в этом случае? Почему?

4.Составьте список вопросов потенциальному пользователю такой программы. Объясните, почему вы хотели бы задать именно их.

2.4.Задание для самостоятельной работы

Используя методы ООП, построить диаграмму последовательностей обращения к объекту какого-либо класса, описанного в диаграмме классов предметной области, соответствующей вашему варианту.

Заключение

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

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

Существуют несколько причины, по которым следует изучать и использовать шаблоны (паттерны) проектирования. Шаблоны могут помочь разработчику в следующем.

Многократно применять высококачественное решение для повторяющихся задач.

Поднять уровень, на котором проблема решается, и избежать нежелательного углубления в детали реализации уже на ранних этапах разработки.

Повысить модифицируемость кода.

Найти альтернативное решение для исключения громоздких иерархий наследования классов.

28

Список литературы

1.Буч Г. Объектно-ориентированное проектирование с примерами применения: Пер. с англ. — М.: Вильямс, 2017. — 720 с.

2.Маклафлин Б., Поллайс Г., Уэст Д.. Объектно-ориентированный анализ и проектирование: Пер. с англ. – СПб.: Питер, 2018. – 608 с.

3.Йордан Э., Аргила К. Объектно-ориентированный анализ и проектирование систем: Пер. с англ. – М.: Лори, 2019. – 264 с.

4.Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес. Приемы объектноориентированного проектирования. Паттерны проектирования: Пер. с англ.,– СПб.: Питер, 2018. – 366 с.

5.Р. Динеш. Spring. Все паттерны проектирования: Пер. с англ.,– СПб.:

Питер, 2019. – 320 с.

6.Тепляков С. Паттерны проектирования на платформе .NET, – СПб.:

Питер, 2018. – 320 с.

29

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]