Глава 2. основные теоретические положения объектно-ориентированного программирования (ооп)

История создания ООП

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

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

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

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

программирование предусматривает механизмы раздельной компиляции частей программы с последующей их сборкой перед выполнением. Модуль является иеким «черным ящиком», который скрывает детали реализации определенных действий, в нем спрятаны данные и алгоритмы их обработки. Через интерфейс модуля можно выполнить необходимые действия, реализованные в нем. Практика программирования показывает, что структурный подход в сочетании с модульным программированием позволяет получать достаточно надежные программы, размер которых не превышает 100 ООО операторов. Узким местом модульного программирования является то, что ошибка в интерфейсе при вызове подпрограммы выявляется только при выполнении программы (из-за раздельной компиляции модулей обнаружить эти ошибки раньше невозможно) [13].

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

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

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

Основные принципы ООП

OOli основано на трех фундаментальных понятиях: инкапсуляция, наследование, полиморфизм.

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

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

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

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

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

Внутри программы классы выполняют следующие основные функции. 1. Классы определяют абстракции «целое/ч^сть» и «общее/частное». Можно использовать классы, чтобы определить реальные объекты, независимо от их сложности. Если объект очень сложен, можно использовать классы для описания отдельных его подэлементов (или подсистем), в этом случае используется абстрация «целое/часть». Например, объект «Линия» состоит из объектов

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Полиморфизм

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

Рассмотрим полиморфизм на примере объектов следующих классов: ' TEllipseBtn (скругленная кнопка), TSquareBtn (квадратная кнопка) и TCircleBtn (круглая кнопка), TRectBtn (прямоугольная кнопка). Предположим, что каждый из этих классов является потомком базового класса ТВtn (обобщенная кнопка), который содержит виртуальный метод Draw. Все потомки TBtn также содержат метод Draw, но каждый из них рисует свою фигуру - окружность, квадрат, прямоугольник или эллипс. Любой из этих объектов можно присвоить переменной типа TBtn, и эта переменная TBtn будет вести себя по-разному в зависимости от типа присвоенного ей объекта. Другими словами, кнопка типа TBtn будет рисоваться круглой, если установить ее равной объекту типа TCircleBtn.

Пример 2.1. Реализация полиморфизма.

var

Buttonl : TBtn;

Button2 : TCircleButton;

Button3 : TEllipseButton;

begin

............... {инициализация кнопок и др. действия}

Buttonl := ButtoftZ;

Buttonl.Draw; // Рисует квадрат

.......... „(др. действия}

Buttonl := Button3;

Buttonl.Draw; // Рисует эллипс

end;

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

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

Parent := Child;//Меньшее устанавливается равным большему-так можно Child := Parent;//Большее устанавливается равным меньшему-так нельзя

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

Parent := Child;

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

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

Более детально указанные ключевые принципы полиморфизма будут рассмотрены позже, при обсуждении реализации полиморфизма в Object Pascal.

§2.3. Классификация языков ООП

Язык, поддерживающий ООП должен обладать, по крайней мере, следующими тремя признаками, выражающими три степени объектной ориентированности: « основанные на объектах языки поддерживают объекты, то есть элементы с состояниями и операциями над ними;

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

10. объектно-ориентярованаые языки имеют наследование и полиморфизм.

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

. Чистые и гибридные языки ООП. Чистые языки ООП - это языки, которые не допускают других моделей программирования. Например, нельзя написать подпрограмму, если она не метод класса, нельзя описывать глобальные переменные. Примеры - SmallTalk и Eiffel. Гибридные языки программирования сочетают в себе элементы структурного (процедурного) программирования и ООП. Примеры гибридных языков: С++ и Object Pascal.

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

Наши рекомендации