Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
LR_OOP.doc
Скачиваний:
61
Добавлен:
10.03.2016
Размер:
268.8 Кб
Скачать

Введение в объектно-ориентированное программирование

Из истории языков программирования

Любая методология программирования имеет свою концептуальную основу. Эволюция языков программирования направлена на ускорение процесса создания надежных программных средств.

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

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

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

Исходя из этих проблем, лучшими программистами 70-х годов (Дейкстра, Вирт, Дал, Хоар, Йордан, Константин, Майерс и другие) были разработаны строгие правила ведения проектов, которые получили название структурной методологии.

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

Еще одним толчком к изменению способа программистского мышления стало опубликованное письмо Дейкстры редактору одного из научных издательств, которое было озаглавлено «Оператор GOTO нужно считать вредным». Это письмо вызвало острую полемику среди программистов того времени, но в итоге победило все-таки структурное мышление, которое кроме Дейкстры активно поддерживали профессор Цюрихского технического университета Вирт (Wirth) и профессор Оксфордского университета Хоар (Hoare). Одним из результатов полемики было доказательство того, что любая программа может быть написана с использованием только простой последовательности операторов, итеративной конструкции типа while и конструкции выбора case, а оператор goto не является необходимой управляющей конструкцией в структурном программировании. К сожалению, споры об операторе goto имели один отрицательный «побочный эффект» – довольно часто программирование без оператора goto стало отождествляться со всем структурным программированием.

Однако цели структурного программирования намного глобальнее и серьезнее, а именно:

  • Обеспечить дисциплину программирования;

  • Улучшать читабельность программы. Читабельность улучшается, если придерживаться следующих правил:

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

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

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

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

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

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

Основные принципы структурной методологии

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

  • Принцип формальности. Слово «формальность» предполагает строгий методический подход.

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

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

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

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

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

Языки программирования, которые поддерживают эту модель, называются структурно-ориенированными или процедурными. Глав­ное внимание в них уделяется построению процедур (подпрог­рамм) и, как следствие, решению следующих вопросов: пере­дача аргументов в процедуры; получение вычисленных зна­чений из процедур; внутренняя организация процедур и т. п. Типичным примером процедурно-ориентированного языка является Фортран – первый и все еще один из наиболее популярных языков программирования. Далее поя­вилось целое поколение языков указанного типа: Алгол-60, Алгол-68, Паскаль, Си и другие. Последовательное использование идеи процедурного структурирования программ привело к созданию обширных библиотек программирования, содержащих множество сравнительно небольших процедур, из которых, как из кирпичиков, можно строить «здание» программы.

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

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

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

- договориться об используемых именах в программе для глобальных переменных, поскольку использование од­ного имени для разных переменных (разными програм­мистами) приводит к ошибке;

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

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

Непосредственная поддержка модульного программиро­вания воплощена в языке Модула-2.

И, наконец, объединение структур данных и операций над ними (методов) привело к появлению понятия класса, объекта. Начиная с языка Симула-67 в программировании наметился новый подход, который получил название объектно-ориентированного программирования (ООП). Его руководящая идея заключается в стремлении связать данные с обрабатывающими эти данные процедурами в единое целое – объект. Характерной чертой объектов является инкапсуляция (объединение) данных и алгоритмов их обработки, в результате чего и данные, и процедуры во многом теряют самостоятельное значение. Фактически объектно-ориентированное программирование можно рассматривать как модульное программирование нового уровня, когда вместо во многом случайного, механического объединения процедур и данных акцент делается на их смысловую связь.

Понятие объектно-ориентированного программирования

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

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

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

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

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

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

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

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

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

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

Абстрактные типы данных

Понятие абстрактных типов дан­ных является ключевым в программировании. Абстракция подразумевает разделение и независимое рассмотрение ин­терфейса и реализации.

Рассмотрим пример. Все мы смотрим телевизионные про­граммы. Назовем телевизор модулем или объектом. Этот объект имеет интерфейс с пользователем, т. е. средства управ­ления (совокупность кнопок), воспроизведения изображения и звука. Чем совершеннее интерфейс, тем удобнее телевизор в использовании. Мы переключаем программы, нажимая опре­деленные кнопки, и при этом не задумываемся о физических процессах, происходящих в телевизоре. Об этом знают спе­циалисты. Когда мы выбираем телевизор, нас интересуют его цена и эксплуатационные параметры, т. е. качество изобра­жения, звука и т. п. Однако нас не интересует то, что находит­ся внутри. Другими словами, мы возвращаемся к свойствам объекта (модуля), какими являются интерфейс и реализация. Основная цель абстракции в программировании как раз и заключается в отделении интерфейса от реализации.

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

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

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

Объекты и классы

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

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

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

В программировании поведение объекта иногда можно представить в виде его состояния (лампочка горит или не горит – переменная равна 1 или 0). Но движение автомобиля уже одной переменной не опишешь, нужна специальная процедура, которая будет описывать это движение.

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

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

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

Определим теперь понятия состояния, поведения и иденти­фикации объекта.

Состояние объекта объединяет все его поля данных (статический компонент, т.е. неизменный) и текущие значения каждо­го из этих полей (динамический компонент, т.е. обычно изменяющийся).

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

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

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

Базовые принципы ООП

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

  1. пакетирование или инкапсуляция;

  2. наследование;

  3. полиморфизм;

  4. передача сообщений.

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

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

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

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

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

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

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

Наследование

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

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

По Гради Бучу «наследование – это такое отношение между объектами, когда один объект повторяет структуру и поведение другого».

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

Типы верхних уровней объектно-ориентированной иерархии, как правило, не имеют конкретных экземпляров объектов. Не существует, например, конкретного живого организма, который бы сам по себе назывался «млекопитающее» или «птица». Такие типы называют абстрактными. Конкретные экземпляры объектов имеют, как правило, типы самых нижних уровней ОО-иерархии: «крокодил Гена» – конкретный экземпляр объекта типа «крокодил», «кот Матроскин» – конкретный экземпляр объекта типа «кошка».

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

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

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

Когда вы строите новый класс, наследуя его из сущест­вующего класса, можно:

  • добавить в новый класс новые компоненты-данные;

  • добавить в новый класс новые компоненты-функции;

  • заменить в новом классе наследуемые из старого класса компоненты-функции.

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

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

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

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

Таким образом, в нашем примере с объектами-животными действие «бежать» будет называться полиморфическим действием, а многообразие форм проявления этого действия – полиморфизмом.

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

Название

Смысл

Конструктор

Создает и инициализирует объект

Деструктор

Освобождает объект, т.е. разрушает его

Модификатор

Изменяет состояние объекта

Селектор

Считывает состояние объекта без изменения этого состояния

Итератор

Организует доступ ко всем частям объекта в строго определенной последовательности

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

Пример объектной декомпозиции

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

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

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

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

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

Класс или объект – это структура данных, которая содержит поля и методы. Как всякая структура данных она начинается зарезервированным словом и закрывается оператором end. Формальный синтаксис не сложен: описание объектного типа получается, если в описании записи заменить слово record на слово object или class и добавить объявление функций и процедур, действующих в данном объекте.

Type <имя типа объекта>= object

<поле>;

<поле>;

….

<метод>;

<метод>;

end;

В ObjectPascal существует специальное зарезервированное слово class для описания объектов, заимствованное из С++.

Type <имя типа объекта>= class

<поле>;

….

<метод>;

<метод>;

end;

ObjectPascal поддерживает обе модели описания объектов.

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

Директивы private, protected, public. Перечисленные директивы предназначены для ограничения доступа к элементам класса. Использование директив при описании класса необязательно.

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

Секция protected содержит защищенные элементы, которые доступны в пределах модуля, содержащего определение класса, и внутри классов- потомков.

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

В общем виде описание нового класса имеет следующий синтаксис:

Type <имя типа объекта>= class

Private

<скрытые_элементы_класса>

Protected

<защищенные_элементы_класса>

Public

<общедоступные_элементы_класса>

End;

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

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

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

Пример. Вводится объектный тип «предок», который имеет поле данных Name (имя) и может выполнять два действия:

  • провозглашать: «Я – предок!»;

  • сообщать свое имя.

Type tPredoc = object

Name: string; {поле данных объекта}

Procedure Declaration; {объявление методов}

Procedure MyName; {объекта}

End;

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

Procedure tPredoc.Declaration; {реализация метода объекта}

begin

writeln(‘Я – предок!’);

end;

Procedure tPredoc.MyName; {реализация метода объекта}

begin

writeln(‘Я – ’, Name);

end;

Внутри описания методов на поля и методы данного типа ссылаются просто по имени. Так метод MyName использует поле Name без явного указания его принадлежности объекту так, если бы выполнялся неявный оператор with <переменная_типа_объект> do.

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

…….{объявление объектного типа и описание его методов}

var v1: tPredoc; {объявление экземпляра объекта}

begin

v1.Name:= ‘Петров Николай Иванович’;

v1.Declaration;

v1.MyName

end.

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

Я – предок!

Я – Петров Николай Иванович

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

Например, в тексте программы вместо операторов

v1.Name:= ‘Петров Николай Иванович’;

v1.Declaration;

v1.MyName

возможно использование оператора with такого вида

with v1 do

begin

Name:= ‘Петров Николай Иванович’;

Declaration;

MyName

End;

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

Иерархия типов (наследование)

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

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

Базовый класс для Производный от CLASS1 Производный

CLASS2 Базовый для CLASS3 от CLASS2

Методы: A,B,CМетоды: Методы:

B,C из CLASS1 B из CLASS1

A,D,E из CLASS2 D,E из CLASS2

A,C,F из CLASS3

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

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

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

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

Пример иерархической структуры объектов

Простое, немножественное Множественное

наследование наследование

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

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

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

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

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

Правило 4. Дочерний тип может доопределить произвольное число собственных методов и информационных полей.

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

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

Продолжим рассмотрение нашего примера. В дополнение к введенному нами типу предка tPredoc можно ввести типы потомков:

tуре tSon = оbject (tPredoc) {Тип, наследующий tPredoc}

procedure Declaration; {перекрытие методов предка}

procedure МуName (Predoc : tPredoc);

end;

tуре tGrandSon= оbject (tSon) {Тип, наследующий tSon}

procedure Declaration;{перекрытие методов предка}

end;

Имя типа предка приводится в скобках после слова оbject. Мы породили наследственную иерархию из трех типов: tSon («сын») наследник типу tPredoc, а тип tGrandSon («внук») ­- типу tSon. Тип tSon переопределяет методы Declaration и МуNаmе, но наследует поле Name. Тип tGrandSon переопределяет только метод Declaration и наследует от общего предка поле Name, а от своего непосредственного предка (типа tSon) переопределенный метод Declaration.

Давайте разберемся, что именно мы хотим изменить в родительских методах. Дело в том, что «сын» должен провозглашать несколько иначе, чем его предок, а именно сообщить ‘Я – отец!’

procedure tSon.Declaration; {реализация методов объектов — потомков}

begin

writeln ('Я— отец!');

end;

А называя свое имя, «сын» должен сообщить следующие сведения:

  • Я <фамилия имя отчество>

  • Я – сын <фамилия имя отчество своего предка>

procedure tSon.МуName (predoc : tPredoc);

begin

inherited МуName; {вызов метода непосредственного предка}

writeln ('Я — сын ', predoc.Name, ‘а’ );

end;

В нашем примере потомок tSon из метода МуName вызывает одноимен­ный метод непосредственного предка типа tPredoc. Такой вызов обес­печивается директивой inherited, после которой указан вызываемый метод непосредственного предка. Если возникает необходимость вызвать метод отдаленного предка в каком-нибудь дочернем типе на любом уровне иерархии, то это можно сделать с помощью уточненного идентификатора, т.е. указать явно имя типа родительского объекта и через точку – имя его метода:

TPredoc.MyName;

Теперь давайте разберемся с «внуком». Метод, в котором «внук» называет свое имя, в точности такой же, как и у его непосредственного предка (типа tSon), поэтому нет необходимости этот метод переопределять, этот метод лучше автоматически наследовать и пользоваться им как своим собственным. А вот в методе Declaration нужно провозгласить ‘Я – внук!’, поэтому метод придется переопределить.

procedure tGrandSon.Declaration;

begin

writeln ('Я — внук!');

end;

Рассмотрим пример программы, в которой определим экземпляр типа tPredoc, назовем его «дед», экземпляр типа tSon – «отец», и экземпляр типа tGrandSon – «внук». Потребуем от них, чтобы они представились.

{заголовок программы}

……………….

{раздел описания типов, в том числе и объектных типов tPredoc, tSon, tGrandSon}

{Обратите внимание! Экземпляры объектных типов можно описать как типизированные константы, что мы для примера и сделали ниже}

Const

ded: tPredoc = (Name: 'Петров Николай Иванович');

otec: tSon = (Name: 'Петров Сергей Николаевич');

vnuk: tGrandSon= (Name: 'Петров Олег Сергеевич');

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

begin

ded. Declaration; {вызов методов общего предка}

ded. МуName;

writeln;

otec.Declaration;

otec.MyName(ded); {вызов методов объекта otec типа tSon}

writeln;

vnuk.Declaration; {вызов методов объекта vnuk типа tGrandSon}

vnuk.MyName(otec);

end.

Наша программа выведет на экран:

Я — предок!

Я — Петров Николай Иванович

Я — отец!

Я — Петров Сергей Николаевич

Я — сын Петров Николай Ивановича

Я внук!

Я — Петров Олег Сергеевич

Я — сын Петров Сергей Николаевича

Обратите внимание, что в заголовке процедуры tSon.MyName в качестве параметра приведен тип данных tPredoc, а при использовании этой процедуры ей передаются переменные как типа tPredoc, так и типа tSon. Это возможно, так как пре­док совместим по типу со своими потомками. Обратное несправедливо. Если мы заменим в заголовке процедуры tSon.MyName при описании параметров тип tPredoc на tSon, компилятор укажет на несовместимость типов при использовании перемен­ной ded в строке otec.MyName(ded).

Полиморфизм и виртуальные методы

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

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

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

В рассмотренном выше примере во всех трех объектных типах tPredoc, tSon и tGrandSon действуют одноименные методы Declaration и MyName. Но в объектном типе tSon метод MyName выполняется несколько иначе, чем у его предка. А все три одноименных метода Declaration для каждого объекта выполняются по-своему.

Методы объектов бывают статическими, виртуальными и динамическими.

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

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

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

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

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

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

procedure Method (список параметров); virtual;

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

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

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

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

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

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

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

constructor Method (список параметров);

Зарезервированное слово constructor заменяет слова procedure и virtual.

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

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

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

Заголовок деструктора выглядит таким образом:

destructor Done;

Основное назначение деструкторов – уничтожение VMT данного объекта. Часто деструктор не выполняет других действий и представляет собой пустую процедуру.

destructor Done;

begin end;

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