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

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

1. Введение

Концепция объектно-ориентированного программирования подразумевает,

что основой управления процессом реализации программы является передача

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

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

этом состоит главное отличие ООП от процедурного программирования, где

отдельно определённые структуры данных передаются в процедуры (функции) в

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

состоит из объектов – отдельных фрагментов кода, обрабатывающего данные,

которые взаимодействуют друг с другом через определённые интерфейсы.

Объектно-ориентированный язык программирования должен обладать

следующими свойствами:

1. абстракции – формальное о качествах или свойствах предмета путем

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

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

он манипулирует, и защищающего их от внешних помех и некорректного

использования;

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

свойства другого, т.е. поддерживается иерархической классификации;

4. полиморфизма – свойства, позволяющего использовать один и тот же

интерфейс для общего класса действий.

Разработка объектно-ориентированных программ состоит из следующих

последовательных работ:

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

задачи;

- определение закрытых данных (данных состояния) для выбранных

объектов;

- определение второстепенных объектов и их закрытых данных;

- определение иерархической системы классов, представляющих

выбранные объекты;

- определение ключевых сообщений, которые должны обрабатывать

объекты каждого класса;

- разработка последовательности выражений, которые позволяют

решить поставленную задачу;

- разработка методов, обрабатывающих каждое сообщение;

- очистка проекта, то есть устранение всех вспомогательных

промежуточных материалов, использовавшихся при проектировании;

- кодирование, отладка, компоновка и тестирование.

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

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

их содержания и поведения в пределах класса. Конструкция «класс»

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

Инкапсуляция как бы скрывает и подробности внутренней реализации типов, и

внешние операции и функции, допустимые для выполнения над объектами этого

типа.

2. Что такое объектно-ориентированное программирование

Элементы объектно-ориентированного программирования (ООП) появились в

начале 70-х годов в языке моделирования Симула, затем получили свое

развитие, и в настоящее время ООП принадлежит к числу ведущих технологий

программирования.

Основная цель ООП, как и большинства других подходов к

программированию – повышение эффективности разработки программ. Идеи ООП

оказались плодотворными и нашли применение не только в языках

программирования, но и в других областях Computer Science, например, в

области разработки операционных систем.

Появление ООП было связано с тем наблюдением, что компьютерные

программы представляют собой описание действий, выполняемых над различными

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

записи в базах данных или совокупности числовых значений. В традиционных

методах программирования изменение данных или правил и методов обработки

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

существенное изменения программы – это большая неприятность для

программиста, так как при этом увеличивается вероятность ошибок, вследствие

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

ООП позволяет выйти из такой ситуации с минимальными потерями, сводя

необходимую модификацию программы к её расширению и дополнению. Необходимо

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

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

идей и методов ООП может существенно упростить разработку и отладку сложных

программ.

Мы уже привыкли использовать в своих программах процедуры и функции

для программирования тех сложных действий по обработке данных, которые

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

было важным шагом на пути к увеличению эффективности программирования.

Подпрограмма может иметь формальные предметы, которые при обращении к ней

заменяются фактическими предметами. В этом случае есть опасность вызова

подпрограммы с неправильными данными, что может привести к сбою программы и

её аварийному завершению при выполнении. Поэтому естественным обобщением

традиционного подхода к программированию является объединение данных и

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

3. Объекты

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

объекта. Объект имеет определённые свойства. Состояние объекта задаётся

значениями его признаков. Объект «знает», как решать определённые задачи,

то есть располагает методами решения. Программа, написанная с

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

между собой.

Ранее отмечалось, что программная реализация объекта представляет

собой объединение данных и процедур их обработки. Переменные объектного

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

можно лишь формально назвать переменной. Его описание даётся в предложение

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

обычная переменная.

В отличие от типа «запись», объектный тип содержит не только поля,

описывающие данные, но также процедуры и функции, описания которых

содержится в описании объекта. Эти процедуры и функции называют методами.

Методам объекта доступны его поля. Следует отметить, что методы и их

параметры определяются в описании объекта, а их реализация даётся вне этого

описания, в том мест программы, которое предшествует вызову данного метода.

В описании объекта фактически содержаться лишь шаблоны обращения к методам,

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

параметров и их типов при обращении к методам. Вот пример описания

объекта[1]:

Type

Location = object

X,Y: Integer;

Procedure Init(InitX, InitY: Integer);

Function GetX: Integer;

Function GetY: Integer;

End;

Здесь описывается объект, который может использоваться в дальнейшем,

скажем, в графическом режиме и который предназначен для определения

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

с помощью зарезервированных слов

object…end, между которыми находиться описание полей и методов. В нашем

примере объект содержит два поля для хранения значений графических

координат, а так же для описания процедуры и двух функций - это методы

данного объекта. Процедура предназначена для задания первоначального

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

4. Инкапсуляция

Инкапсуляция является важнейшим свойством объектов, на котором

строится объектно-ориентированное программирование. Инкапсуляция

заключается в том, что объект скрывает в себе детали, которые несущественны

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

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

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

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

имеется «не-ООП» программа, предназначенная для начисления заработной платы

сотрудникам некой организации, а в программе имеются два массива. Один

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

сотрудников (для составления отчёта для налоговой инспекции). Что

произойдёт, если программист случайно перепутает эти массивы? Очевидно, для

бухгалтерии начнутся тяжёлые времена. «Жёсткое» связание данных и процедур

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

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

соответствующие методы.

В нашем примере описание объекта процедура инициализации Init и

функции GetX и GetY уже не существуют как отдельные самостоятельные

объекты. Это неотъемлемые части объектного типа Location. Если в программе

имеется описание нескольких переменных указанного типа, то для каждой

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

данных, а указатели на точки входа в процедуру и функции – общие. Вызов

каждого метода возможен только с помощью составного имени, явно

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

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

Наследование – это ещё одно базовое понятие объектно-ориентированного

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

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

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

собственные поля и методы или заменить («перекрыть») их своими методами.

Пример описания объекта-наследника даётся ниже:

Tipe

Point = object(Location)

Visible: Boolean;

Procedure Int(IntX, IntY: Integer);

Procedure Show;

Procedure Hide;

Function IsVisible: Boolean;

Procedure MoveTo(NewX, NewY: Integer);

End;

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

точку, а родителем – объект Location. Наследник не содержит описание полей

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

слова object. Из методов наследника можно вызывать методы родителя. Для

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

Объект-родитель может быть уже в составе оттранслированного модуля.

В чём привлекательность наследования? Если некий объект был уже

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

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

возникает необходимость некоторой модификации как данных, так и методов их

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

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

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

затрат времени на отладку и тестирование. Во втором случае часть этой

работы оказывается выполненной, что сокращает время на разработку новой

программы. Программист при этом может и не знать деталей реализации объекта-

родителя.

В нашем примере к объекту, связанному с определением положения

графического элемента, просто добавилось новое поле, описывающее признак

видимости графической точки, и несколько новых методов, связанных с режимом

отображения точки и её преобразованиями.

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

Наследование позволяет создавать иерархические, связанные отношениями

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

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

в нашей графической программе необходимо определить объект Circle, который

является потомком другого объекта Point:

Type

Circle = object (point)

Radius: Integer;

Procedure Show;

Procedure Hide;

Procedure Expand(ExpandBy: Integer);

Procedure Contact(ContactBy: Integer);

End;

Новый объект Circle соответствует окружности. Поскольку свойства

окружности отличаются от свойств точки, в объекте-наследнике придется

изменять процедуры Show и Hide, которые отображают окружность и удаляют её

изображение с экрана. Может оказаться, что метод Init (см. предыдущий

пример) объекта Circle, унаследованный от объекта Point, также использует

методы Show и Hide, впредь во время трансляции объекта Point использует

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

Можно, конечно, попытаться «перекрыть» метод Init. Чтобы это сделать, нам

придётся полностью воспроизвести текст метода. Это усложни работу, да и не

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

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

Для решения этой проблемы используется виртуальный метод. Связь между

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

время трансляции (это называется ранним связанием), а во время выполнения

программы (позднее связание.

Чтобы использовать виртуальный метод, необходимо в описании объекта

после заголовка метода добавить ключевое слово virtual. Заголовки

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

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

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

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

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

procedure заменяется словом constructor. Действия обратные действиям

конструктора, выполняет ещё один специальный метод – деструктор. Он

описывается словом destructor.

Конструктор выполняет действия по подготовке позднего связывания. Эти

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

которая в дальнейшем используется для поиска методов. Таблица содержит

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

имени определяется адрес, а затем по этому адресу передается управление.

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

методов, что позволяет одному и тому же оператору вызывать разные

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

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

скопировать этот экземпляр во все остальные. Каждый объект должен иметь

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

противном случае возможен сбой в работе программы.

Заметим, что конструктор или деструктор, могут быть «пустыми», то есть

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

трансляции ключевых слов construct и destruct.

7. Динамическое создание объектов

Переменные объектного типа могут быть динамическими, то есть

размещаться в памяти только во время их использования. Для работы с

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

Dispose. Обе процедуры в этом случае содержат в качестве второго параметра

вызов конструктора или деструктора для выделения или освобождения памяти

переменной объектного типа:

New(P, Construct)

или

Dispose(P, Destruct)

Где P – указатель на переменную объектного типа, а Construct или

Destruct – конструктор и деструктор этого типа.

Действие процедуры New в случае расширенного синтаксиса равносильно

действию следующей пары операторов:

New(P);

P^.Construct;

Эквивалентом Dispose является следующее:

P^Dispose;

Dispose(P)

Применение расширенного синтаксиса не только улучшает читаемость

исходного кода, но и генерирует более короткий и эффективный исполняемый

код.

8. Полиморфизм

Полиморфизм заключается в том, что одно и то же имя может

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

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

метод Init действовал по-разному в зависимости от того, является объект

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

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

конкретной ситуации, принимается во время выполнения программы.

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

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

свойства совместимости с другими объектами своего родителя. В правой части

оператора присваивания вместо типов родителя можно использовать типы

наследника, но не наоборот. Таким образом, в нашем примере допустимы

присваивания:

Var

Alocation : Location;

Apoin : Point;

Acircle : Circle;

Alocation :=Apoint

Apoint := Acrcle;

Alocation := Acircle;

Дело в том, что наследник может быть более сложным объектом,

содержащим поля и методы, поэтому присваиваемые значения экземпляра объекта-

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

неопределёнными и, следовательно, представляет потенциальную опасность. При

выполнении оператора присвоения копируются только те поля данных, которые

являются общими для обоих типов.

-----------------------

[1] Выполняется на языке Turbo Pascal, начиная с версии 5.0. Далее все

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