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

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. Далее все
примеры даны для выполнения на этом языке программирования.