Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
СПО юнита 2.doc
Скачиваний:
71
Добавлен:
17.11.2019
Размер:
5.82 Mб
Скачать

1.4 Объектно-ориентированные системы программирования

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

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

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

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

Рисунок 2. Архитектора программы с глобальной областью данных

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

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

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

Рисунок 3. Архитектура программы, использующей подпрограммы с локальными данными

Была создана технология, получившая название «структурное программирование».

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

Основные принципы проектирования и реализации программного обеспечения:

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

– модульное программирование, рекомендующее определенные структуры алгоритмов и стиль программирования;

– использование при программировании трех структур управления: следование, выбор и повторение;

– отказ от безусловных передач управления и ограниченное использование глобальных переменных;

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

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

Сначала определяют общую структуру программы в виде:

– последовательности подзадач: ввод данных, преобразование данных, вывод данных;

– альтернативы подзадач: добавление записей к файлу или их поиск;

– повторения подзадачи: циклически повторяемая обработка данных (рисунок 4).

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

Поддержка принципов структурного программирования была заложена в основу так называемых процедурных языков программирования. Как правило, они включали основные «структурные» операторы управления, поддерживали вложение подпрограмм, локализацию и ограничение области «видимости» данных. Среди наиболее известных языков этой группы стоит назвать PL/1, Алгол-68, Паскаль, С.

Рисунок 4. Основные конструкции структурной декомпозиции:

а – следование; б – ветвление; в – цикл-пока

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

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

Рисунок 5. Архитектура программы, использующей модули

Эту технологию поддерживают современные версии языков Паскаль и С/C++, языки Ада и Модула.

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

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

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

Основные понятия и принципы объектно-ориентированного программирования

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

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

Принципы ООП проще всего понять на примере программ моделирования. Каждый предмет или процесс обладает набором статических и динамических характеристик – свойствами и поведением. Поведение объекта зависит от его состояния и внешних воздействий.

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

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

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

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

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

Объект – это инкапсулированная абстракция с четко определенным интерфейсом.

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

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

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

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

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

Преимущества ООП:

– использование при программировании понятий, более близких к предметной области;

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

– возможность повторного использования кода за счет наследования;

– сравнительно простая возможность модификации программы;

– возможность создания библиотек объектов.

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

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

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

Основной недостаток ООП – снижение быстродействия за счет более сложной организации программной системы.

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

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

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

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

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

– реализация – совокупность недоступных извне элементов реализации абстракции (внутрен-няя организация абстракции и механизмы реализации ее поведения).

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

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

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

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

В ООП используются два вида иерархии.

Иерархия «целое/часть» показывает, что некоторые абстракции включены в рассматривае-мую абстракцию как ее части. Этот вариант иерархии используется в процессе разбиения системы на части на разных этапах проектирования. На логическом уровне – при декомпозиции предметной области на объекты, на физическом уровне – при декомпозиции системы на модули и при выделении отдельных процессов в мультипроцессорной системе.

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

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

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

Использование принципа типизации обеспечивает:

– раннее обнаружение ошибок, связанных с недопустимыми операциями над программными объектами – ошибки обнаруживаются на этапе компиляции программы при проверке допустимости выполнения данной операции над программным объектом;

– упрощение документирования;

– возможность генерации более эффективного кода.

Тип может связываться с программным объектом статически (тип объекта определен на этапе компиляции – раннее связывание) и динамически (тип объекта определяется только во время выполнения программы – позднее связывание). Реализация позднего связывания в языке программирования позволяет создавать переменные – указатели на объекты, принадлежащие различным классам (полиморфные объекты), что существенно расширяет возможности языка.

Параллелизм – свойство нескольких абстракций одновременно находиться в активном состоянии, т.е. выполнять некоторые операции.

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

Устойчивость – свойство абстракции существовать во времени независимо от процесса, породившего данный программный объект, и/или в пространстве, перемещаясь из адресного пространства, в котором он был создан.

Различают:

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

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

– глобальные объекты, существующие пока программа загружена в память;

– сохраняемые объекты, данные которых хранятся в файлах внешней памяти между сеансами работы программы.

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

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

Особое место занимают объектные модели C++ Builder. Они включают средства, обеспечивающие эффективное создание сложных систем. На базе этих моделей созданы визуальные среды для разработки Windows. Сложность программирования «под Windows» удалось существенно снизить за счет создания специальных библиотек объектов, «спрятавших» многие элементы техники программирования.

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

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

Виды проектирования:

– логическое проектирование, при котором принимаемые решения практически не зависят от условий эксплуатации (операционной системы и используемого оборудования);

– физическое проектирование, при котором приходится принимать во внимание указанные факторы.

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

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

Реализация системы – это процесс поэтапной реализации и подключения классов к проекту.

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

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

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

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

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

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

Объектная декомпозиция

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

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

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

Объектная декомпозиция – процесс представления предметной области задачи в виде совокупности объектов, обменивающихся сообщениями.

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

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

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

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

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

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

– физические и материальные объекты (дом, самолет);

– характеристики объектов (цвет, размер, стиль);

– место действия (офис, аэропорт);

– роль человека (менеджер по продажам, пассажир);

– абстрактные понятия;

– организация (конкретная фирма);

– событие (встреча, продажа);

– совокупность записей (книга учета).

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

Полученный перечень понятий – кандидатов на роль объектов предметной области – анализируют, определяя:

– целесообразность реализации понятия в виде объекта;

– взаимосвязи понятий между собой.

При этом различают два типа связей: ассоциацию и обобщение.

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

* – от 0 до бесконечности;

<целое>… * – от заданного числа до бесконечности;

<целое> – точно определенное количество объектов;

<целое1>, <целое2> – несколько вариантов точного количества объектов;

<целое1>…<целоеn> – диапазон объектов.

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

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

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

Описание объектов

Объект – это тип данных, поэтому он определяется в разделе описания типов. В некоторых языках программирования объектный тип называют классом. Объект похож на тип record, но кроме полей данных в нем можно описывать методы. Методы – подпрограммы, предназначенные для работы с полями объекта. Внутри объекта описываются только заголовки методов:

type имя = object

[private]

описание полей

[public]

заголовки методов

end:

Поля и методы называются элементами объекта. Их видимостью управляют директивы private и public. Ключевое слово private (закрытые) ограничивает видимость перечисленных после него элементов файлом, в котором описан объект. Действие директивы распространяется до другой директивы или до конца объекта. В объекте может быть произвольное количество разделов private и public. По умолчанию все элементы объекта считаются видимыми извне, то есть являются public.

Поля объекта описываются аналогично обычным переменным: для каждого поля задается его имя и тип. Тип может быть любым, кроме типа того же объекта, но может быть указателем на этот тип. Значения полей определяют состояние объекта.

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

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

Описание методов размещается вне объекта в разделе описания процедур и функций, при этом имени метода предшествует имя объекта, отделенное точкой.

Перед использованием объект требуется инициализировать, то есть присвоить начальные значения его полям. В языке Паскаль процедуре инициализации объекта дается имя init. Поля объекта используются внутри методов без указания имени объекта.

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

Как правило, поля объекта объявляют как private, а методы – как public, однако это не догма. Если поле объекта представляет собой свойство, которое концептуально входит в интерфейс объекта, и пользователь должен иметь право устанавливать и получать его без каких-либо ограничений, его можно поместить в раздел publiс. Метод может быть предназначен для вызова только из других методов, в этом случае его можно поместить в разделе private.

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

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

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

Экземпляры объектов

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

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

Если объектный тип описан в модуле, для создания в программе переменных этого типа следует подключить модуль в разделе uses:

Доступ к элементам объекта осуществляется либо с использованием составного имени, либо с помощью оператора with:

Если объект описан в модуле, получить или изменить значения элементов со спецификатором private в программе можно только через обращение к соответствующим методам.

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

Классы

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

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

Реализация объединения данных с определенными видами их обработки делает классы пригодными для описания состояния и поведения моделей реальных объектов. Совокупность полей класса определяется множеством аспектов состояния объекта с точки зрения решаемой задачи, а совокупность методов – множеством аспектов поведения объекта (рисунок 6).

Рисунок 6. Соответствие объекта-абстракции классу и объектам-переменным

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

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

<имя объекта>.<имя поля> или <имя объекта>.<имя метода>.

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

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

Класс <имя класса>

интерфейс <объявление полей и методов класса, к которым возможно обращение извне>

реализация <объявление полей и методов класса, к которым невозможно обращение извне>

Конец описания.

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

Рисунок 7. Интерфейс и реализация класса

Объект должен быть размещен в памяти (создан) и удален из памяти (уничтожен). Создание и уничтожение объектов выполняется статически или динамически.

Статическое создание объектов выполняется в процессе компиляции программы, а статическое уничтожение – при завершении программы: объект удаляется из памяти вместе с программой.

Динамическое создание и уничтожение объектов выполняется в процессе работы программы специальными командами.

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

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

Основные и дополнительные средства разработки классов

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

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

Исходный класс, на базе которого выполняется конструирование, часто называют родителем, а производный – потомком. Если непосредственный родитель единственный, то наследование называется простым, а если таких классов несколько – то множественным (рисунок 8). Класс или классы-родители и класс-потомок образуют иерархию «общее-частное».

Рисунок 8. Иерархии классов для простого и множественного наследования

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

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

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

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

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

Термин «полиморфизм» в программировании используется для обозначения встроенного механизма определения соответствия кода функции типу параметров.

Термины, связанные с конкретными механизмами реализации полиморфизма:

– чистый полиморфизм – используется для обозначения того, что один код функции может по-разному интерпретироваться в зависимости от типа аргументов, используется в языках высокого уровня абстракции, например в языке Lisp или Smalltalk;

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

– переопределение – используется в ООП при необходимости задания различных реализаций одноименных методов в иерархии классов.

Различают:

– простой полиморфизм – конкретный метод определяется типом объекта при компиляции программы (раннее связывание). Одноименные методы при использовании простого полиморфизма называют статическими полиморфными;

– сложный полиморфизм (полиморфные объекты) – конкретный метод определяется типом объекта при выполнении программы (позднее связывание). Одноименные методы при использовании сложного полиморфизма называют виртуальными полиморфными;

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

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

Методы, для которых должно реализовываться позднее связывание, получили название динамических полиморфных или виртуальных. Для их описания в языках программирования используется зарезервированное слово virtual.

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

Реализация механизма позднего связывания осуществляется с использованием специальной таблицы – таблицы виртуальных методов (ТBM). ТBМ создается для каждого класса, имеющего собственные или наследующего виртуальные методы, и содержит адреса аспектов виртуальных методов класса. Объекты классов с виртуальными методами включают невидимое поле, содержащее адрес ТВМ своего класса (рисунок 9). При вызове виртуального метода для объекта по этому адресу происходит обращение к ТВМ класса, по которой определяется требуемый метод.

Рисунок 9. Реализация механизма позднего связывания

При использовании полиморфных объектов возникают проблемы с доступом к полям объекта, описанным в классе-потомке: указатель на объект класса-родителя связан с описанием полей класса-родителя. Поля, описанные в классе-потомке, для него «невидимы» (рисунок 10).

Рисунок 10. Указание типа объекта, адресуемого указателем родительского класса

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

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

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

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

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

Дополнительные средства и приемы разработки классов

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

Рисунок 11. Прямое и косвенное обращения к классу

Реализация метаклассов базируется на использовании специальных таблиц, в которых хранится информация о классе: имя класса, имя класса-родителя, адреса методов. Эти таблицы используются во время выполнения программы и получили название RTTI (Run Time Type Information – «информация о типе времени выполнения»).

Эти же таблицы используются для реализации следующих операций:

– операции проверки принадлежности объекта заданному классу или его потомкам;

– операции уточнения класса объекта, которая отличается от операции явного переопре-деления типа тем, что перед переопределением выполняется проверка принадлежности объекта данному классу или его потомкам;

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

Делегирование – «заимствование» методов у объектов других классов. В отличие от переопределения, делегирование позволяет определять различное поведение объектов, принадлежащих одному классу. Заимствование методов возможно как в пределах класса или иерархии классов, так и у объектов классов других иерархий.

Метод при этом вызывается косвенно, через указатель на него. Язык, в котором реализуется делегирование, должен обеспечивать возможность определения указателей на методы. Назначение или замена метода осуществляется присваиванием соответственного значения соответствующему указателю на метод (рисунок 12).

Рисунок 12. Делегирование метода

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

Статическое делегирование используют в двух случаях:

– если требуемое поведение объекта уже описывалось для объектов другого класса – в этом случае делегирование позволяет избавиться от повторов в программе;

– если класс объявлен с не полностью определенным поведением объектов и его поведение уточняется для конкретных экземпляров объектов.

Динамическое делегирование используется при создании объектов с изменяемым поведением, когда вариант поведения определяется некоторыми внешними обстоятельствами.

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

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

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

Рисунок 13. Построение классов на базе контейнерного класса и класса элемента

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

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

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

<очередной элемент>:=<первый элемент>

цикл-пока <очередной элемент> определен

<выполнить обработку>

<очередной элемент>:=<следующий элемент>

все-цикл

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

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

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

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

При использовании шаблона указывают его имя и соответствующее значение параметра. Например:

Список (Запись1) или Список (Запись2)

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

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

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

– генерация информации о возникновении исключительной ситуации – генерация исклю-чения;

– обработка этой информации – перехват исключения.

При перехвате исключений используется стек вызовов, в котором в момент передачи управления очередной подпрограмме фиксируется адрес возврата. Например, если метод А вызывает метод В, а тот, в свою очередь, вызывает подпрограмму (или метод) С, то в стеке последовательно записываются: адрес возврата в А и адрес возврата в В (рисунок 14).

Рисунок 14. Организация стека вызовов

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

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

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

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

При возникновении исключений возможны три варианта действий системы:

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

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

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

Исключение может инициироваться:

– операционной системой при получении сигналов от контролирующих схем;

– операционной системой при обнаружении некорректных ситуаций в работе программ самой операционной системы;

– библиотечными программами среды разработки;

– непосредственно разрабатываемой программой.

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

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

Совместимость типов объектов

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

– между экземплярами объектов;

– между указателями на экземпляры объектов;

– между параметрами и аргументами подпрограмм.

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

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

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

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

В языке Паскаль определена функция typeof, которая определяет фактический тип объекта. Ее параметром может быть объектный тип, объект или указатель на объект.

typeof(тип | объект | указатель_на_объект)

Если параметр подпрограммы является указателем на объект, передаваемым по значению, то аргумент может быть указателем на этот же объект или на любого из его потомков. Например, если заголовок процедуры имеет вид

procedure checkp(pl : pmonstr: var p2 : pmonstr);

первым параметром в нее можно передавать указатели как на объекты типа monstr, так и на любые производные объекты. На месте второго параметра может быть только указатель типа pmonstr.

Виртуальные методы

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

procedure attack; virtual;

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

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

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

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

– переопределять виртуальный метод в каждом из потомков не обязательно: если он выпол-няет устраивающие потомка действия, он будет унаследован;

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

Вызов виртуального метода выполняется так: из объекта берется адрес его VMT, из VMT выбирается адрес метода, а затем управление передается этому методу. Таким образом, при использовании виртуальных методов из всех одноименных методов иерархии всегда выбирается тот, который соответствует фактическому типу вызвавшего его объекта.

Поскольку связь с VMT устанавливается в самом начале конструктора, в его теле также можно пользоваться виртуальными методами.

Виртуальные методы незаменимы и при передаче объектов в подпрограммы. В заголовке подпрограммы описывается либо объект базового типа, передаваемый по адресу, либо указатель на этот объект, а при вызове в нее передается объект или указатель производного класса. В этом случае виртуальные методы, вызываемые для объекта из подпрограммы, будут соответствовать типу аргумента, а не параметра.

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

Конструкторы

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

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

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

Объект может содержать несколько конструкторов. Повторный вызов конструктора вреда программе не наносит, а вот если конструктор вообще не вызвать и попытаться использовать виртуальный метод, поведение программы не определено. Если включить режим контроля границ (с помощью директивы {$R+}), в этой ситуации произойдет ошибка времени выполнения. Конструктор должен быть вызван для каждого создаваемого объекта. Присваивание одного объекта другому возможно только после конструирования обоих.

Деструкторы

Для корректного освобождения памяти из-под полиморфных объектов следует использовать вместе с процедурой Dispose специальный метод – деструктор. В документации по Паскалю ему рекомендуется давать имя done.

Для правильного освобождения памяти деструктор записывается вторым параметром процедуры Dispose:

Dispose(pm, done);

Для простых объектов деструктор может быть пустым, а для объектов, содержащих динамические поля, в нем записываются операторы освобождения памяти для этих полей. В деструкторе можно описывать любые действия, необходимые для конкретного объекта, например закрытие файлов. Исполняемый код деструктора никогда не бывает пустым, потому что компилятор по служебному слову destructor вставляет в конец тела метода операторы получения размера объекта из VMT. Деструктор передает этот размер процедуре Dispose, и она освобождает количество памяти, соответствующее фактическому типу объекта.

Вызов деструктора вне процедуры Dispose память из-под объекта не освобождает.

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

Динамические объекты

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

Для выделения памяти под объекты используются процедура и функция new. Например, если определены указатели

можно создать объекты с помощью вызовов

new(pm): {или pm : = new(pmonstr): }

new(pd): {или pd := new(pdaemon): }

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

Обращение к методам динамического объекта выполняется по обычным правилам Паскаля, например

pm^.draw: pm^.attack:

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

pm : = new(pdaemon. init(1. 1. 1. 1. 1)):

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

Для освобождения памяти, занятой объектом, применяется процедура Dispose:

Dispose(pm):

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

Разработка объектно-ориентированного программного обеспечения

По времени в жизненном цикле процесса выделяют четыре этапа:

– начало – спецификация представления продукта;

– развитие – планирование необходимых действий и требуемых ресурсов;

– конструирование – построение программного продукта в виде серии инкрементных ите-раций;

– переход – внедрение программного продукта в среду пользователя.

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

Рабочие потоки процесса имеют следующее содержание:

– сбор требований – описание того, что система должна делать;

– анализ – преобразование требований к системе в классы и объекты, выявляемые в предмет-ной области;

– проектирование – создание статического и динамического представления системы, выпол-няющего выявленные требования и являющегося эскизом реализации;

– реализация – производство программного кода, который превращается в исполняемую систему;

– тестирование – проверка всей системы в целом.

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

Между артефактами потоков существуют зависимости. Например, модель Use Case, генерируемая в ходе сбора требований, уточняется моделью анализа из процесса анализа, обеспечивается проектной моделью из процесса проектирования, реализуется моделью реализации из процесса реализации и проверяется тестовой моделью из процесса тестирования.

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

– бизнес-модель – определяет абстракцию организации, для которой создается система;

– модель области определения – фиксирует контекстное окружение системы;

– модель Use Case – определяет функциональные требования к системе;

– модель анализа – интерпретирует требования к системе в терминах проектной модели;

– проектная модель – определяет словарь проблемы и ее решение;

– модель размещения – определяет аппаратную топологию, в которой исполняется система;

– модель реализации – определяет части, которые используются для сборки и реализации физической системы;

– тестовая модель – определяет тестовые варианты для проверки системы;

– модель процессов – определяет параллелизм в системе и механизмы синхронизации.

Технические артефакты подразделяются на четыре основных набора:

– набор требований – описывает, что должна делать система;

– набор проектирования – описывает, как должна быть сконструирована система;

– набор реализации – описывает сборку разработанных программных компонентов;

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

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

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

Набор реализации группирует все данные о программных элементах, образующих систему (программный код, файлы конфигурации, файлы данных, программные компоненты, информацию о сборке системы).

Набор размещения группирует всю информацию об упаковке, отправке, установке и запуске системы.