Какой вид декомпозиции используется в ооп
Перейти к содержимому

Какой вид декомпозиции используется в ооп

  • автор:

Объектно-ориентированная декомпозиция — что это такое

vedro-compota's picture

Сразу отметим, что есть и более общее понятие декомпозиции кода.

Объектно-ориентированная декомпозиция — это разбиение системы на сущности, являющиеся какими-либо объектами действующими в той ситуации, которую как раз и моделирует система.

Такая декомпозиция считается «более продвинутой» (при разработке сложных систем) в отличии от Алгоритмической
при которой просто происходит разбиение функционала — ни по отношению к сущности (типа -набор методов в данном классе реализует функционал описываемого объекта) , а просто «сверху вниз» — то есть об общего к частному.

Например при алгоритмической композиции — в случае если требуется описать порцесс кормления коровы — никто не будет создавать два класса , например =

— как в случае объектно-ориентированной декомпозиции, а просто начнут уточнять функционал, например так (вначале написав базовую функцию)=

  • 1) покормить_корову()
  • 2) купить_корм() + покормить_корову()
  • 3) купить_корм() + насыпать_корм_в_кормушку() + убедится_что_корова_начала_есть()
  • 4) и т.д.

то есть при алгоритмической декомпозиции функционал просто уточняется и разбивается на части — без относительно того к каким объектам он имеет отношения или какими объектами данные функции выполняются.

Key Words for FKN + antitotal forum (CS VSU):

  • Log in to post comments
  • 29255 reads

humanmashine's picture

Sat, 09/15/2012 — 19:04

Если честно, то нигде не

Если честно, то нигде не видел, чтобы в ООП применяли разбиение по функционалу, описанное выше, не считая людей, которые иногда получают от меня звание «быдлокодер» ))) не в обиду будет сказано, я и сам бываю часто «быдлокодером» от незнания.
Но во всей литературе по ООП, ранее, я встречал как раз описание объектов, а не разбиение по функционалу (этот метод можно встретить в основном в языках не поддерживающих ООП, или поддерживающих наряду с функциональным программированием С/С++)
Так что когда я впервые начал программировать на Qt-C++ то уже там было как раз рекомендовано разбиение по объектам, а Брюс Эккель так и писал, что представьте перед собой волшебную шляпу и доставайте от туда объекты, необходимые для решения задачи)))) Таким образом в программе будут именно объекты вида «корова».
В виду этого, для меня было открытие, что существует такое термин «Объектно-ориентированная декомпозиция». Немного повергло в шок то, что кто-то в ООП разбивает по функционалу — люди, не делайте так.

  • Log in to post comments

vedro-compota's picture

Sat, 09/15/2012 — 19:42

неееееее.

неееееее.
я думал, что пишу непонятно,но всё рано продолжал))

по функционалу разбиение происходит при алгоритмической декомпозиции
я просто там всё сравниваю со всем и потому читать надо внимательнее)))

_____________
матфак вгу и остальная классика =)

  • Log in to post comments

humanmashine's picture

Sun, 09/16/2012 — 11:12

§ 46. Что такое ООП?

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

С каждым годом производительность компьютеров росла, и человек «поручал» им всё более и более трудоёмкие задачи. Компьютеры следующих поколений стали использоваться для создания сложных информационных систем (например, банковских) и моделирования процессов, происходящих в реальном мире. Новые задачи требовали более сложных алгоритмов, объём программ вырос до сотен тысяч и даже миллионов строк, число переменных и массивов измерялось в тысячах.

Программисты столкнулись с проблемой сложности, которая превысила возможности человеческого разума. Один человек уже не способен написать надёжно работающую серьезную программу, так как не может «охватить взглядом» все её детали. Поэтому в разработке большинства современных программ принимает участие множество специалистов. При этом возникает новая проблема: нужно разделить работу между ними так, чтобы каждый мог работать независимо от других, а потом готовую программу можно было бы собрать вместе из готовых блоков, как из кубиков.

Как отмечал известный нидерландский программист Эдсгер Дейкстра, человечество ещё в древности придумало способ управления сложными системами: «разделяй и властвуй». Это означает, что исходную систему нужно разбить на подсистемы (выполнить декомпозицию) так, чтобы работу каждой из них можно было рассматривать и совершенствовать независимо от других.

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

В конце 60-х годов XX века появилась новая идея — применить в разработке программ тот подход, который использует человек в повседневной жизни. Люди воспринимают мир как множество объектов — предметов, животных, людей — это отмечал ещё в XVII веке французский математик и философ Рене Декарт. Все объекты имеют внутреннее устройство и состояние, свойства (внешние характеристики) и поведение. Чтобы справиться со сложностью окружающего мира, люди часто не вникают в детали внутреннего устройства и игнорируют многие свойства объектов, ограничиваясь лишь теми, которые необходимы для решения их практических задач. Такой приём называется абстракцией.
Абстракция — это выделение существенных характеристик объекта, отличающих его от других объектов.

Для разных задач существенные свойства одного и того же объекта могут быть совершенно разными. Например, услышав слово «кошка», многие подумают о пушистом усатом животном, которое мурлыкает, когда его гладят. В то же время ветеринарный врач представляет скелет, ткани и внутренние органы кошки, которую ему нужно лечить. В каждом из этих случаев применение абстракции даёт свою модель одного и того же объекта, поскольку различны цели моделирования.

Как применить принцип абстракции в программировании? Поскольку формулировка задач, решаемых на компьютерах, всё более приближается к формулировкам реальных жизненных задач, возникла такая идея: представить программу в виде множества объектов (моделей), каждый из которых обладает своими свойствами и поведением, но его внутреннее устройство скрыто от других объектов. Тогда решение задачи сводится к моделированию взаимодействия этих объектов. Построенная таким образом модель задачи называется объектной. Здесь тоже идёт проектирование «сверху вниз», только не по алгоритмам (как в процедурном программировании), а по объектам. Если нарисовать схему такой декомпозиции, она будет представлять собой граф, так как каждый объект может обмениваться данными со всеми другими (рис. 7.2).

Здесь А, Б, В и Г — объекты «верхнего уровня»; Бг, Б2 и Б3 — под объекты объекта Б и т. д.

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

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

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

© Вопросы и задания

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

2. Что такое декомпозиция, зачем она применяется?

3. Что такое процедурное программирование? Какой вид декомпозиции в нём используется?

4. Какие проблемы в программировании привели к появлению ООП?

5. Как выполняется декомпозиция алгоритмов в процедурных языках программирования?

6. Что такое абстракция? Зачем она используется в обычной жизни?

7. Объясните, как связана абстракция с моделированием.

8. Какие преимущества даёт объектный подход в программировании?

9. Какой вид декомпозиции используется в ООП?

10. Что такое интерфейс? Приведите примеры объектов, у которых одинаковый интерфейс и разное устройство.

а) «Проблемы процедурного программирования»

б) «Глобальные переменные: за и против»

в) «ООП: достоинства и недостатки»

NEWOBJ.ru → Введение в ООП с примерами на C# →

Начнем с определения термина «парадигма». С некоторой точки зрения разработка программного обеспечения – это всегда процесс структурирования и формализации неструктурированных и неформализованных знаний. В конечном счете при разработке программы мы стремимся что-то «объяснить» вычислительной машине, понимающей только некоторый формальный язык. Так, в ходе анализа требований нечеткие цели, задачи, идеи, пожелания пользователей (заказчиков) уточняются и структурируются в технические задания (требования, проекты и другие документы), содержащие относительно внятные цели, сценарии использования, структурированное описание предметной области, упорядоченные функциональные требования и тому подобные сведения. В ходе проектирования и программирования более или менее структурированная задача транслируется программистами с естественного языка на формальный (в математическом смысле) язык программирования 1 . Естественный внутренний язык мышления человека может принципиально отличаться от языка программирования, соответственно, в ходе этой трансляции может полностью измениться способ описания задачи. Насколько разными при этом могут быть типы языков программирования?

В самой общей классификации таких типов, называемых парадигмами программирования, может быть два. Рассмотрим их на примере следующей задачи. Имеется массив чисел – показаний датчика давления. Требуется найти среднее арифметическое отклонений от граничной величины 50 для всех значений, превышающих эту величину. Это вполне четкая формулировка задачи на естественном языке. Рассмотрим следующее решение на языке программирования C#:

 // Объявляем и инициализируем массив вещественных чисел, // хранящий показания датчика. float[] data = new float[] < 35, 52, 44, 58, 59, 38, 33 >; // Граничная величина, по условию задачи равна 50. float threshold = 50; // Переменная для хранения суммы отклонений показаний граничной величины, // для показаний, ее превышающих. Начальное значение – 0. float sum = 0; // Количество показаний, превышающих граничную величину. // Начальное значение – 0. int count = 0; // Перебираем все показания. for (int i = 0; i < data.Length; i++) < // Если i-е показание превышает граничную величину. if (data[i] >threshold) < // . сохраняем отклонение в сумму. sum += data[i] – threshold; // . и увеличиваем счетчик отклонений. count++; >> // Искомое среднее арифметическое отклонений. float avg = sum / count; 

Читателю, знакомому с основами программирования, этот код, конечно, покажется вполне типичным. Однако рассмотрим другой вариант решения той же задачи:

 float[] data = new float[] < 35, 52, 44, 58, 59, 38, 33 >; float threshold = 50; float avg = data // Выбрать только те элементы i2 последовательности, 
// которые удовлетворяют условию i > threshold
.Where (i => i > threshold) // Вместо каждого элемента i получить значение, // рассчитанное по формуле i – threshold. .Select(i => i - threshold) // Получить среднее значение элементов последовательности. .Avg();

В чем принципиальная разницам между эти двумя решениями? В первом случае мы пошагово описываем алгоритм, то есть инструктируем компьютер, как достичь результата. Во втором же случае мы описываем сам результат, то есть что нужно достичь, но не описываем, как это сделать. Мы пишем: найди среднее значение ( avg ) отклонений ( i — threshold ) для элементов, удовлетворяющих заданному условию ( i > threshold ). При этом последовательность выполнения действий может быть другой: компилятор сам решит, как именно лучше получить искомое значение. Например, вероятно, он не будет обходить последовательность три раза (один раз – чтобы отфильтровать ненужные, второй – чтобы найти отклонения, третий – чтобы посчитать среднее значение), а так или иначе совместит эти задачи. Можно сказать, что использованные методы Where , Select , Avg являются не инструкциями, а требованиями 3 .

Приведенные примеры – это примеры императивного и декларативного программирования соответственно.

Императивное программирование – программирование вычислительной машины путем описания последовательности инструкций, выполняемых одна за другой; программист однозначно определяет последовательность шагов, которые необходимо выполнить для достижения результата. Соответствующие языки, программы на которых представляют собой последовательность инструкций, называются императивными языками программирования.

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

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

Тип языка диктует способ мышления о программе. Процитируем главного разработчика языка C# [17]: «Язык влияет на то, как мы думаем. Работа программиста состоит, если хотите, в том, чтобы думать. Это сырье, чистая энергия, участвующие в процессе. Язык – это то, что определяет ваше мышление; его задача – помочь вам думать продуктивным образом. Например, языки с поддержкой объектов заставляют вас думать над задачей так. Функциональные языки заставляют вас думать над задачей иначе. Динамические языки заставят вас думать еще как-то. Выбрав язык, вы выбираете особый способ мышления.» Язык программирования определяет не только процесс программирования в узком смысле как написание кода (coding), но также обуславливает подходы к проектированию и анализу системы. Именно в этом смысле обычно используют термин «парадигма»:

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

Мы намеренно подчеркнули, что деление языков на императивные и декларативные определяют парадигму программирования в широком смысле слова. Возможно ли выделить еще какие-либо парадигмы? В силу некоторой нечеткости термина иногда к базовым парадигмам относят и функциональное программирование, однако мы будем придерживаться распространенной классификации, выделяющей только два типа. Также отметим, что сегодня как альтернативную, «неклассическую» парадигму часто определяют машинное обучение. «В классическом программировании… люди вводят правила (программу) и данные для обработки в соответствии с этими правилами и получают ответы. В машинном обучении люди вводят данные и ответы, соответствующие этим данным, а на выходе получают правила. Эти правила затем можно применить к новым данным для получения оригинальных ответов. В машинном обучении система обучается, а не программируется явно. Ей передаются многочисленные примеры, имеющие отношение к решаемой задаче, а она находит в этих примерах статистическую структуру, которая позволяет системе выработать правила для автоматического решения задачи.» 5

Однако вернемся к классическому программированию, которое мы будем рассматривать в настоящей книге. В более узком смысле выделяют парадигмы программирования на основе более частных классификаций типов языков. Так, в рамках императивной парадигмы выделяют три базовых типа: (1) структурное, или процедурное 6 , (2) объектно-ориентированное и (3) функциональное [Мартин 11]. Далее мы рассмотрим понятие объектно-ориентированного программирования в сравнении с, вероятно, известным читателю процедурным программированием.

§ 2. Алгоритмическая декомпозиция. В процедурном программировании мы представляем программу как иерархию процедур, то есть алгоритмов. Таким образом, при решении задач, мы выполняем алгоритмическую декомпозицию, а основная используемая абстракция, то есть элемент языка, с которым работает программист – алгоритм. Такое структурирование кода диктует архитектуру приложений и подходы к проектированию и анализу. При этом, конечно, процедурное программирование является императивным. Рассмотрим пример: положим, мы создаем систему мониторинга автотранспорта, отображающую на экране карту с текущим положением автобусов. Определим какие процедуры (алгоритмы) нам следует реализовать для решения задачи. Разобьем их на три группы: относящиеся к автобусу, относящиеся к маршруту и относящиеся к карте.

Во-первых, основной алгоритм: (1) «перерисовать положение автобуса на карте с указанием гос. номера, номера маршрута, уровня топлива, вместимости и статуса положения на маршруте («на маршруте» или «сошел с маршрута»)». Он относится к автобусу. Чтобы его реализовать нам потребуется несколько вспомогательных процедур: (2) «определить гос. номер», (3) «определить местоположение», (4) «определить уровень топлива», (5) «определить вместимость», (6) «определить номер маршрута». Все эти вспомогательные алгоритмы будут обращаться к базе данных и получать соответствующую информацию.

Во-вторых, чтобы определить, находиться ли автобус на маршруте или нет, нам потребуется два алгоритма, относящихся к маршруту: (7) «определить координаты границ маршрута (многоугольника)», (8) «определить, находится ли точка с указанными координатами внутри указанного многоугольника (маршрута)».

Наконец, нам потребуются алгоритмы рисования, относящиеся к карте: (9) «нарисовать точку заданного цвета и размера», (10) «добавить на карту заданный текст заданного цвета и размера» (для подписи точки-автобуса).

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

При написании кода эта модель преобразуется в иерархию вызовов процедур в коде – каждому блоку соответствует процедура, использующая (вызывающая) процедуры, соответствующие подчиненным блокам 7 .

Для запуска программы мы вызываем корневой метод для некоторого автобуса:

 int busId = 123; DrawBus (busId); 

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

Обратим внимание, что, давая краткое описание процедурного программирования в начале параграфа, мы указали три характеристики: 1) используемые способы декомпозиции (алгоритмическая), 2) используемые абстракции (процедуры-алгоритмы) и 3) используемые иерархии, упорядочивающие эти абстракции (иерархия вызовов процедур). Дело в том, что декомпозиция, абстракция и иерархия – базовые мыслительные операции на пути от неструктурированных и неформализованных к структурированным и формализованным знаниям. Человек использует их не только в программировании, но и вообще при решении сложных задач: разделяет задачу на части (декомпозиция), выделяет только существенные аспекты (абстракция) и структурирует элементы в иерархии. Читатель может найти подробный анализ этих вопросов применительно к разработке программного обеспечения к книге [Буч 3], где этому посвящен отдельный раздел. Можно сказать, что именно используемые виды этих операций определяют подходы и к кодированию, и к проектированию, и к анализу, то есть определяют парадигму программирования. Дадим следующее уточненное определение:

Парадигма программирования – это совокупность используемых при разработке программного обеспечения способов декомпозиции, видов абстракций и видов иерархий, упорядочивающих используемые абстракции; или: совокупность используемых при разработке программного обеспечения подходов к анализу, проектированию и программированию, обусловленных способами декомпозиции, видами абстракций и видами иерархий, упорядочивающих используемые абстракции.

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

§ 3. Объектная декомпозиция. Еще в 70-х годах отмечалось, что основное концептуальное ограничение процедурного программирования – семантический (смысловой) разрыв между моделью, поддерживаемой языком, и моделью в голове у программиста. Хотя мы и говорим, что язык обуславливает мышление, однако разные языки могут в разной мере соответствовать естественному процессу мышления. В отличие от процедурного подхода основная идея объектно-ориентированного программирования заключается в представлении программы как набора взаимодействующих объектов подобно тому, как мы воспринимаем окружающий мир. Мы видим вокруг себя людей, здания, автомобили, растения – тысячи различных объектов. Каждый из объектов состоит из других объектов (иерархия «часть – целое»). В то же время каждый из объектов относится к некоторому классу (виду, типу) объектов (иерархия «частное – общее»). Сколь угодно сложный объект может быть физически или мысленно разделен на множество более простых. Таким образом, в объектной модели используется: 1) декомпозиция – объектная; 2) абстракция – объект и класс объектов; 3) иерархия – «часть – целое» и «частное – общее».

Покажем это на рассмотренном в предыдущем параграфе примере системы мониторинга автотранспорта. Вместо того, чтобы выполнять алгоритмическую декомпозицию, определяя необходимые нам процедуры, выполним объектную декомпозицию. Во-первых, это, конечно, автобус. Объект «автобус» должен «уметь» перерисовать себя на объекте «карта», что соответствует процедуре 1, выделенной нами в ходе алгоритмической декомпозиции. Также автобус должен уметь определить номер своего маршрута (соответствует процедуре 6). Должен ли автобус уметь определить свою вместимость (процедура 5)? С одной стороны – да. Но мы можем развить объектную модель, указав, что автобус – это разновидность пассажирского транспортного средства (ТС), то есть автобус – «частное» по отношению к пассажирскому ТС в иерархии «частное – общее». Как любое частное, автобус «умеет» все то, что умеет «общее». Поэтому мы можем сказать, что пассажирское ТС умеет определять вместимость (процедура 5), а автобус тоже это умеет, так как он является разновидностью пассажирского ТС. Рассмотрим следующую диаграмму:

Блоки диаграммы показывают классы объектов. Мы пока говорим о «классах» в обычном «не программистском» значении как о выделенных в результате классификации группах однотипных объектов. Блок «Автобус» обозначает класс автобусов, каждый из объектов-автобусов умеет себе перерисовывать и определять номер маршрута. Стрелка в виде не закрашенного треугольника обозначает, что класс объектов «Автобус» является разновидностью класса объектов «Пассажирское ТС», что есть каждый объект-автобус – разновидность объекта-пассажирское ТС и, значит, каждый автобус умеет все то, что умеет пассажирское ТС, здесь: умеет определять свою вместимость.

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

Итак, продолжим построение нашей объектной модели. Если вместимость – характеристика пассажирских ТС, то уровень топлива – характеристика любых ТС. Но характеристика ли это именно ТС? Мы можем еще более детализировать модель, указав, что транспортное средство включает в себя как составную часть топливный бак, а топливный бак включает в себя как составную часть датчик уровня. И уже датчик уровня умеет определять уровень. Покажем это на диаграмме:

Стрелка в виде закрашенного ромба обозначает иерархическое отношение «часть – целое». Отметим, что у нас на диаграмме три промежуточных блока между классом «Автобус» и классом «Датчик уровня», однако датчик уровня – часть автобуса. Покажем это на модели: автобус – разновидность пассажирского ТС, пассажирское ТС – разновидность ТС, ТС включает в себя топливный бак, топливный бак включает в себя датчик давления. Значит автобус также включает в себя и топливный бак, и датчик давления. Эта диаграмма отражает вполне естественные для мышления человека отношения между объектами предметной области.

На следующем рисунке представлена полная диаграмма объектной декомпозиции решаемой задачи:

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

Код перерисовки автобуса выглядит следующим образом 8 :

 int busId = 123; // Создаем в программе объект bus. bus – переменная, тип данных которой – Bus. Bus bus = new Bus (123); // «Говорим» этому объему, чтобы он перерисовал себя. bus.Draw(); 

Что нам дает такая декомпозиция по сравнению с алгоритмической? Ведь обратим внимание, что все процедуры, которые мы выделили при алгоритмической декомпозиции присутствуют и на объектной модели (их номера указаны в скобках). Дело в том, что код программы в объектно-ориентированном программировании оказывается ближе к естественному представлению о предметной области в мышлении разработчика. Это упрощает понимание программы. Проще – значит лучше (быстрее, надежнее, качественнее, дешевле). Это безусловный тезис в сфере разработки программного обеспечения.

Обозначим две базовых идеи, лежащие в основе объектно-ориентированного программирования: 1) абстрактные типы данных и 2) иерархические типы данных.

Абстрактные типы данных – определенные программистом типы данных (в отличие от встроенных типов данных языка, например, int , float ), представляющие (моделирующие в коде) некоторую абстракцию. Такие типы позволяют отвлечься от ненужных для решаемой задачи частностей и писать код с использованием языка предметной области. Например, автобус в нашем коде – абстракция реального автобуса. Реальный автобус имеет значительно более сложную структуру и обладает большими возможностями, но для нашей задачи нужно только то, что мы выделили. Таким образом, мы, во-первых, абстрагируемся от ненужных деталей предметной области, создавая при этом модель на языке этой предметной области. Во-вторых, мы абстрагируемся от реализации автобуса и его возможностей в коде. Это значит, что код, вызывающий методы автобуса, ничего не знает, о том, как автобус реализован, в каких типах данных он хранит те или иные характеристики. Все эти детали коду, вызывающему перерисовку, не нужны.

Вторая базовая идея ООП – иерархические типы. Это определенные программистом типы данных, расширяющие возможности других типов данных. В нашем примере автобус – частный случай транспортного средства. Иерархические типы позволяют отвлечься от деталей частных классов, работая с общим поведением и, наоборот, не дублировать общее поведение, работая с частным, полагаясь на класс, являющийся общим. Так, разрабатывая код для класса «датчик давления» мы можем не думать о том, что этот датчик давления топливного бака именно автобуса. Мы разрабатываем отдельный датчик давления, который сможем использовать и в других транспортных средствах. С другой стороны, разрабатывая код автобуса, мы может не отвлекаться на возможности, реализуемые пассажирским ТС, ТС и датчиками – все это доступно в виде готовых абстракций, реализация которых от автобуса также скрыта. В обоих случая мы работаем только над своей текущей задачей, не отвлекаясь на ненужные частности и зависимости.

Конечно, приведенное описание понятий абстрактных и иерархических типов не претендует на точность и полноту. Каждую из этих идей мы будем подробно рассматривать в соответствующем разделе. Однако, интуитивно понятно, что обе идеи могут существенно упрощать код. Соединение концепции абстрактных типов данных и иерархических типов данных в объектно-ориентированное программирование сформулировано еще на заре развития информационных технологий, «обе концепции действительно являются достижением в искусстве программирования» [Брукс 2].

Приведем классическое определение ООП [Буч 3]:

Объектно-ориентированное программирование (object-oriented programming) – парадигма программирования, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром класса, а классы образуют иерархию наследования.

По сути это определение формулирует три уже рассмотренных нами характеристики парадигмы программирования: 1) способ декомпозиции (здесь – объектная); 2) виды абстракций (объекты и классы); 3) виды иерархии («часть – целое» и «частное – общее»).

Мы рассмотрели концептуальное определение ООП. С технической точки зрения объектно-ориентированное программирование – набор определенных возможностей языка: классы, наследование, виртуальные и абстрактные методы, полиморфизм и другие. Их детальному обсуждение посвящены последующие разделы.

Вопросы и задания

Дайте определение следующим терминам: парадигма программирования, императивное программирование, декларативное программирование, декомпозиция, абстракция, иерархия, процедурное программирование, структурное программирование.

Найдите несколько разных определений объектно-ориентированного программирования и попытайтесь свести их к приведенному в тексте.

Вспомните языки программирования, с которыми вы сталкивались. Они являются императивными или декларативными?

** Вспомните какую-либо задачу, которую вы недавно решали, и попытайтесь выполнить ее объектную декомпозицию.

1. При этом необходимо понимать, что последовательность «анализ – проектирование – программирование» – это не описание организации разработки. Мы лишь обозначили условную классификацию видов процессов. На практике все они встречаются на любом этапе создания программного обеспечения.

2. Здесь i – сокращение от item (элемент), а не от index (индекс).

3. Читатель, знакомый с SQL, увидит, что приведенный код похож на SQL-запрос select avg (i – 50) from data where i > 50 . Действительно, SQL также реализует подход к программированию, основанный на описании требований к результату, а не способа достижения этого результата. Для сравнения: если бы мы решали задачу выборки данных в терминах инструкций, то вместо приведенного запроса, нам бы пришлось описывать алгоритм построчного выбора данных из таблицы, проверки значений, вычисления средних значений, а также работать с индексами, страницами памяти и т. п. Используя же SQL мы возлагаем все эти задачи на СУБД, концентрируясь на сути решаемой проблемы: найти такие-то данные, удовлетворяющие таким-то условиями.

4. Также, часто как синонимы «парадигме программирования» используются какие термины как подход (approach) или стиль.

5. Шолле, Ф. Глубокое обучение на Python.

6. Иногда между этими терминами проводят различия, однако мы будем использовать их как синонимы. Обычно, говоря о процедурном программировании, акцентируют внимание на идее процедуры (метода, функции) и, соответственно, на идее алгоритмической декомпозиции. Говоря о структурном программировании, акцентируют внимание на запрете использования оператора goto (перехода к любой указанной строке кода). Запрет на использование этого оператора как следствие приводит к структурированию программы с использованием условных выражений, циклов и процедур [Мартин 11].

7. Подчеркнем (повторно), что речь не идет об организации процесса разработки: говорить, что сначала составляется диаграмма, подобная приведенной, а потом программист по ней пишет код – огромное упрощение. Однако, концептуально происходит именно так: иерархия алгоритмов в мысленном представлении (более ими менее полном, явном и формализованном) воплощается в иерархии процедур в коде.

8. Как и для алгоритмической декомпозиции, подчеркнем (в третий раз), что речь не идет об организации процесса разработки: говорить, что сначала составляется диаграмма, подобная приведенной, а потом программист по ней пишет код – огромное упрощение.

&copy Тимофей Усов, 2019—2020.

Лекция 2. МОДЕЛИ И ИХ РОЛЬ В СОЗДАНИИ СИСТЕМ. ОБЪЕКТНАЯ МОДЕЛЬ

Одна из основных проблем при создании больших и сложных систем, в том числе ПО, — это проблема сложности. Виды сложности: техническая сложность и сложность управления. Техническая сложность может быть вызвана:

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

Сложность управления порождается следующими причинами:

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

Подход к решению этой проблемы основан на принципе «разделяй и властвуй» (divide et impera). Сложная программная система должна быть разделена на небольшие подсистемы, каждую из которых можно разрабатывать независимо (в какой-то степени) от других. Декомпозиция является главным способом преодоления сложности разработки ПО. Принципы декомпозиции:

  • количество связей между подсистемами должно быть минимальным («низкая связанность» или «слабое зацепление» — Low Coupling);
  • степень взаимодействия внутри каждой подсистемы должна быть максимальной («сильная связность» или «высокая прочность» — High Cohesion).

При разбиении системы на подсистемы необходимо добиться выполнения следующих условий:

  • каждая подсистема должна инкапсулировать свое содержимое (скрывать его от других подсистем);
  • каждая подсистема должна иметь четко определенный интерфейс с другими подсистемами, устанавливающий стандартные ограничения на взаимодействие.

Следование этим правилам увеличивает понятность и модифицируемость создаваемого ПО.

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

В рамках обоих подходов используется понятие архитектуры ПО. Архитектура ПО — набор ключевых правил, определяющих организацию системы:

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

Архитектура ПО многомерна, поскольку различные специалисты работают с её различными аспектами. Различные представления архитектуры служат различным целям (модель «4+1»):

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

Среди 5-ти представлений особое место занимает представление вариантов использования, поскольку оно используется при управлении разработкой, служит своего рода скелетом проекта. В общем случае говорят о модели «N+1», имея в виду что перечень архитектурных представлений может варьироваться, но представление вариантов использования входит в него обязательно.

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

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

«Моделирование является центральным звеном всей деятельности по созданию качественного ПО. Модели строятся для того, чтобы понять и осмыслить структуру и поведение будущей системы, облегчить управление процессом ее создания и уменьшить возможный риск, а также документировать принимаемые проектные решения.»

Архитектурно значимый элемент — это элемент, значительно влияющий на структуру системы, её функциональность, производительность, надежность, защищенность, возможность развития. Подсистемы, их интерфейсы, процессы и потоки управления являются архитектурно значимыми элементами.

Существуют различные графические модели, используемые при разработке ПО: блок-схемы, конечные автоматы, синтаксические диаграммы, семантические сети. Общее их достоинство графических моделей — наглядность.

Визуальное (графическое) моделирование — позволяет описывать проблемы с помощью зримых абстракций, воспроизводящих понятия и объекты реального мира. Моделирование осуществляется при помощи языка моделирования, который включает в себя: элементы модели; нотацию (систему обозначений); руководство по использованию.

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

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

В процессе создания ПО используются следующие виды моделей:

  • модели деятельности организации (или модели бизнес-процессов):
  • модели «AS-IS» («как есть»), отражающие существующее положение;
  • модели «AS-TO-BE» («как должно быть»), отражающие представление о новых процессах и технологиях работы организации;
  • модели проектируемого ПО, которые строятся на основе модели «AS-TO-BE», уточняются и детализируются до необходимого уровня.

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

Для облегчения труда разработчиков и автоматизированного выполнения некоторых рутинных действий используются CASE-средства (Computer Aided Software Engineering). В настоящее время CASE-средства обеспечивают поддержку большинства процессов жизненного цикла ПО, что позволяет говорить о CASE-технологиях разработки ПО. CASE-технология — это совокупность методов проектирования ПО и инструментальных средств для моделирования предметной области, анализа моделей на всех стадиях ЖЦ ПО и разработки ПО.

Объектная модель является концептуальной базой объектно-ориентированного подхода (ООП).

Проблемы, стимулировавшие развитие ООП:

  • Необходимость повышения производительности разработки за счет многократного (повторного) использования ПО.
  • Необходимость упрощения сопровождения и модификации разработанных систем (локализация вносимых изменений).
  • Облегчение проектирования систем (за счет сокращения семантического разрыва между структурой решаемых задач и структурой ПО).

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

Краткая история ООП:

  • 1967: язык Simula — 1ый среди объектно ориентированных;
  • 1970-е: Smalltalk — получил довольно широкое распространение;
  • 1980-е: Теоретические основы, C++, Objective-C;
  • 1990-е: Методы OOA и OOD (Booch, OMT, . ), появился язык Java;
  • 1997: Принят стандарт OMG UML 1.1.

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

Объектная модель является естественным способом представления реального мира. Она является концептуальной основой ООП. Основными принципами ее построения являются:

  • абстрагирование;
  • инкапсуляция;
  • модульность;
  • иерархия.
  • типизация;
  • параллелизм;
  • устойчивость (persistence).

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

Инкапсуляция — локализация свойств и поведения в рамках единственной абстракции (рассматриваемой как «черный ящик»), скрывающей реализацию за общедоступным интерфейсом. При инкапсуляции отделяется внутреннее устройство объекта от его внешнего поведения. Объектный подход предполагает, что внутренние ресурсы объекта, скрыты от внешней среды. Абстрагирование и инкапсуляция являются взаимодополняющими принципами.

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

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

Тип — точная характеристика некоторой совокупности однородных объектов, включающая структуру и поведение.

Типизация — способ защититься от использования объектов одного класса вместо другого, или, по крайней мере, управлять таким использованием.

При строгой типизации (например, в языке Оберон) запрещается использование объектов неверного типа, требуется явное преобразование к нужному типу. При менее строгой типизации такого рода запреты ослаблены. В частности, допускается полиморфизм — многозначность имен. Одно из проявлений полиморфизма, использование объект подтипа (наследника) в роли объекта супертипа (предка).

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

Устойчивость — способность объекта сохранять свое существование во времени и/или пространстве (адресном, в частности при перемещении между узлами вычислительной системы). В частности, устойчивость объектов может быть обеспечена за счет их хранения в базе данных.

Переходим к основным понятиям объектно-ориентированного подхода (элементам объектной модели). К ним относятся: объект; класс; атрибут; операция; полиморфизм; наследование; компонент; пакет; подсистема; связь.

Объект — осязаемая сущность (tangible entity) — предмет или явление (процесс), имеющие четко выраженные границы, индивидуальность и поведение Любой объект обладает состоянием, поведением и индивидуальностью. Состояние объекта определяется значениями его свойств (атрибутов) и связями с другими объектами, оно может меняться со временем. Поведение определяет действия объекта и его реакцию на запросы от других объектов. Поведение представляется с помощью набора сообщений, воспринимаемых объектом (операций, которые может выполнять объект). Индивидуальность — это свойства объекта, отличающие его от всех других объектов.

Структура и поведение схожих объектов определяют общий для них класс. Класс — это множество объектов, связанных общностью свойств, поведения, связей и семантики. Любой объект является экземпляром класса. Определение классов и объектов — одна из самых сложных задач объектно-ориентированного проектирования.

Атрибут — поименованное свойство класса, определяющее диапазон допустимых значений, которые могут принимать экземпляры данного свойства. Атрибуты могут быть скрыты от других классов, это определяет видимость атрибута: рublic (общий, открытый); private (закрытый, секретный); protected (защищенный). Мощность (кратность) атрибута показывает, сколько значений хранится в одном экземпляре атрибута. Если кратность больше 1, то атрибут описывает массив, список. Мощность указывается в профиле атрибута, примеры: name: string [1]; phones: string [*]. Описаны атрибуты: имя — строка; телефоны — список строк. По умолчанию кратность — 1.

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

Операция — это услуга, которую можно запросить у любого объекта данного класса. Операции реализуют поведение экземпляров класса. Описание операции включает четыре части: имя; список параметров; тип возвращаемого значения; видимость. Реализация операции называется методом.

Результат операции зависит от текущего состояния объекта. Виды операций:

  • Операции реализации (implementor operations) — реализуют требуемую функциональность.
  • Операции управления (manager operations) управляют созданием и уничтожением объектов (конструкторы и деструкторы).
  • Операции доступа (access operations) — так называемые, get-теры, set-теры — дают доступ к закрытым атрибутам.
  • Вспомогательные операции (helper operations) — непубличные операции, служат для реализации операций других видов.

Объект может быть абстракцией некоторой сущности предметной области (объект реального мира) или программной системы (архитектурный объект).

Сравнение архитектур традиционной и ОО-системы:

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

Понятие полиморфизма может быть интерпретировано, как способность объекта принадлежать более чем одному типу. Полиморфизм — способность скрывать множество различных реализаций под единственным общим именем или интерфейсом. Интерфейс — это совокупность операций, определяющих набор услуг класса или компонента. Интерфейс не определяет внутреннюю структуру, все его операции открыты. Пример, одна и та же операция рассчитатьЗарплату может иметь три различные реализации в трех различных классах: СлужащийСПочасовойОплатой, СлужащийНаОкладе, ВременныйСлужащий.

Компонент — это относительно независимая и замещаемая часть системы, выполняющая четко определенную функцию в контексте заданной архитектуры.

Компонент представляет собой физическую реализацию проектной абстракции и может быть: компонентом исходного кода (cpp-шник); компонентом времени выполнения (dll, ActiveX и т. п.); исполняемый компонентом (exe-шник). Компонент обеспечивает физическую реализацию набора интерфейсов. Компонентная разработка (component-based development) представляет собой создание программных систем, состоящих из компонентов (не путать с объектно-ориентированным программированием (ООП).

ООП — способ создания программных компонентов, базирующихся на объектах.

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

Пакет — это общий механизм для организации элементов в группы. Это элемент модели, который может включать другие элементы. Каждый элемент модели может входить только в один пакет. Пакет является:

  • средством организации модели в процессе разработки, повышения ее управляемости и читаемости;
  • единицей управления конфигурацией.

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

Между элементами объектной модели существуют различные виды связей.

Соединение (link) — физическая или концептуальная связь между объектами, позволяющая им взаимодействовать.

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

Пример показывает, что ассоциация ВладеетАкциями (OwnsStock) между классами Персона и Компания может иметь несколько экземпляров — соединений. Обратите внимание, что два соединения, являющиеся экземплярами одной и той же ассоциации, не могут связывать одни и те же объекты дважды.

Агрегация— более сильный тип ассоциативной связи между целым и его частями (пример: автомобиль и мотор). Композиция — усиленная агрегация, когда часть не может существовать без целого (пример: университет, факультет, кафедра). Композиция и агрегация транзитивны, в том смысле, что если B является частью A, и C является частью B, то C также является частью A (но на диаграмме связи, возникающие за счет транзитивности, явно не изображаются).

Соединения, являющиеся экземплярами композиций или агрегаций также изображаются с ромбами на полюсах.

Ассоциации (включая агрегации и композиции) характеризуются: направлением, именем, ролевыми именами участников связи, мощностями. Направление указывает ход сообщений. По умолчанию ассоциации двунаправлены, т. е. сообщения могут исходить из любого конца ассоциации. Если введено ограничение по направлению, то добавляется стрелка на конце связи. Ассоциации может быть дано имя, полюсам (концам) ассоциации могут быть назначены роли. Например, у ассоциации между классом Компания и классом Персона полюсу класса Компания может быть назначена роль Работодатель, а другому полюсу — роль Служащий. Понятие ассоциации связано с понятием атрибута. При наличии ассоциации между классами их экземпляры соединены ссылками, то есть имеют атрибуты, значениями которых являются ссылки на экземпляры связанного класса

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

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

Мощность(multiplicity) показывает, как много объектов может участвовать в соединениях — экземплярах ассоциации. Мощность — это количество объектов одного класса (с той стороны связи, где приписана мощность), которые соединены с одним объектом другого класса (на другом конце связи). Для каждой ассоциации существуют два указателя мощности — по одному на каждом конце связи. Для соединений мощность не указывают, так как на любом конце соединения находится ровно один объект. Обозначения мощностей в UML:

0..* или * Ноль или больше

1..* Один или больше

0..1 Ноль или один

2..4 Заданный диапазон

244

Частный случай ассоциации — класс ассоциации, при помощи которого атрибуты и операции можно привязать непосредственно к соединению. Т. е. при наличии класса ассоциации с каждым соединением связан его экземпляр (в примере для каждой связанной пары курс — студент есть экземпляр класса оценка):

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

Обратите внимание, как переместились мощности связей.

В UML 2.0 к объектной модели добавлено понятие N-арной ассоциации (для каждой комбинации объектов не более одной связи):

На рисунке представлена тернарная ассоциация и класс-ассоциация, связывающая проект, программистов, занятых в проекте, и языки, на которых они программируют в проекте. Некоторым аналогом тернарной ассоциации является реляционное отношение над тремя доменами (таблица со столбцами проект, программист, язык). Экземплярами N-арных ассоциаций являются N-арные соединения. Так, программистка Мэри в одном проекте пишет на Коболе, а в другом на Си. Заметим, что мощности на концах тернарной ассоциации не воспрещают Мэри писать в одном и том же проекте на разных языках (хотя на диаграмме объектов такое не показано). Единственное ограничение, наложенное в данном случае, — два разных тернарных соединения не могут связывать одну и ту же тройку объектов — например: Мэри, проект accountingSystem и Кобол.

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

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

Во второй модели тройка объектов лекторПетров, семестрСедьмой и курсЛекцийМатан могут быть соединены более чем единожды посредством разных экземпляров класса ЧитаемыйКурс.

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

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

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

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

  • в сигнатуре операции одного класса есть аргумент — объект другого класса;
  • в методе одного класса есть локальный объект другого класса;
  • результатом операции одного класса является экземпляр другого класса.

Зависимость между пакетами возникает при импорте описаний элементов одного пакета в другой.

Обобщение — это связь «тип — подтип». Оно реализует механизм наследования (inheritance), поддерживает полиморфизм. Наследование — это построение новых классов на основе существующих с возможностью добавления или переопределения свойств (атрибутов) и поведения (операций). Изображается как стрелка с треугольным наконечником, исходящая из наследника и указывающая на родителя. Еще одним обозначением является выделение курсивом имен абстрактных классов (не имеющих собственных экземпляров).

Общие атрибуты, операции и/или отношения отображаются на верхнем уровне иерархии. Заметим, что ассоциации класса-предка наследуются классами потомками, т. е. экземпляры потомков могут иметь соединения того же рода, что и экземпляры родительского класса. Действует принцип подстановки Лисковой (Liskov substitution principle — LSP), по которому любое утверждение, справедливое для экземпляров класса, сохраняет справедливость для экземпляров всех его подклассов. Например, из LSPследует, что экземпляр подкласса подкласса (класса «внука») может рассматриваться как экземпляр подкласса или экземпляр исходного класса (своего «деда»).

В объектной модели наследование может быть множественным. На связи наследования могут накладываться ограничения. Например, если необходимо, множественное наследование внутри некоторой иерархии классов может быть запрещено (над связью указывается ключевое слово: disjoint>). Можно ограничить набор классов-наследников на некотором уровне иерархии наследования указав ограничение comlpete>.

Обобщение рассматривается не только для классов, но и для ассоциаций. В этом случае стрелка обобщения соединяет две ассоциации. Заметим, что в моделях такое встречается редко. Ниже приведен пример, когда ассоциация между рейсом и самолетом переопределяется в классах наследниках ассоциациями-наследницами.

Реализация— связь между контрактом (интерфейсом, вариантом использования) и его исполнением (классом, подсистемой, компонентой и т. п.). Изображается пунктирной стрелкой с треугольным наконечником, исходящей из исполнения (класса, подсистемы) и указывающей на контракт (интерфейс). Для реализации интерфейсов есть альтернативная «леденцовая» или «гнездовая» нотация. При её использовании интерфейс изображается кружком, а связь реализации — сплошной линией без стрелки, идущей к нему.

Литература к лекции 2

  • Буч Г. Объектно-ориентированный анализ и проектирование с примерами приложений. 3-е изд. — М.: Вильямс, 2008. — Часть I.
  • Вендров А. М. Проектирование программного обеспечения экономических информационных систем. 2-е изд. — М.: Финансы и статистика, 2005. — Глава 2.
  • Рамбо Дж., Блаха М. UML 2.0. Объектно-ориентированное моделирование и разработка. 2-е изд. — СПб.: Питер, 2006. — Главы 2-3.
  • Грэхем И. Объектно-ориентированные методы. Принципы и практика. 3-е изд.: Пер. с англ. — М.: Вильямс, 2004. — Глава 1.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *