Объектно-ориентированное программирование
Содержание:
- Основные понятия ООП
- Понятие объекта
Основные понятия ООП
Объектно-ориентированное программирование (ООП) представляет собой новый этап развития современных концепций построения языков программирования. Здесь получили дальнейшее развитие принципы структурного программирования — структуризация программ и данных, модульность и т. д.
В основе ООП лежит понятие объекта (object), сочетающего в себе данные и действия над ними. Объект в некотором роде похож на стандартный тип-запись (record), но включает в себя не только поля данных, но также и подпрограммы для обработки этих данных, называемые методами. Таким образом, в объекте сосредоточены его свойства и поведение. Идеи создания нового типа-объект были уже заложены при введении процедурного типа, отождествляющего между собой данные и действия над ними. Фактически, тип-объект включает в себя, помимо данных, элементы процедурных типов, правда, несколько иначе оформленные и с расширенным набором особенностей, о которых будет сказано ниже.
Введение нового типа данных потребовало пересмотреть некоторые концепции языка Паскаль: ввести новые понятия, как, например, инкапсуляция, наследование, полиморфизм и виртуальность, новые зарезервированные слова (constructor, destructor, inherited, object, private, public, virtual), изменить уже существующие подпрограммы (подпрограммы New и Dispose).
Объект состоит из следующих трех частей:
- имени объекта;
- состояния (переменных состояния);
- методов (операций).
Можно дать обобщающее определение: объект ООП — это совокупность переменных состояния и связанных с ними методов (операций). Упомянутые методы определяют, как объект взаимодействует с окружающим миром.
Под методами объекта понимают процедуры и функции, объявление которых включено в описание объекта и которые выполняют действия. Возможность управлять состояниями объекта посредством вызова методов в итоге и определяет поведение объекта. Совокупность методов часто называют интерфейсом объекта.
ООП характеризуется тремя основными свойствами:
- инкапсуляция (encapsulation),
- наследование (inheritance),
- полиморфизм (polymorphism).
Инкапсуляция — это механизм, который объединяет данные и методы, манипулирующие этими данными, и защищает и то и другое от внешнего вмешательства или неправильного использования. Когда методы и данные объединяются таким способом, создается объект. Пример: перемещаемый по экрану отрезок, определяемый координатами своих концов (данные), и процедурой, обеспечивающей это перемещение (метод).
Наследование позволяет создавать иерархию объектов, начиная с некоторого простого первоначального (предка) и кончая более сложными, но включающими (наследующими) свойства предшествующих элементов (потомки). Эта иерархия в общем случае может иметь довольно сложную древовидную структуру. Каждый потомок несет в себе характеристики своего предка (содержит те же данные и методы), а также обладает собственными характеристиками. При этом наследуемые данные и методы описывать у потомка нет необходимости. Пример: в качестве такой иерархии можно рассмотреть точку на экране дисплея, задаваемую своими координатами (предок); отрезок, задаваемый координатами двух точек — его концов (потомок точки); перемещаемый отрезок, задаваемый координатами своих концов и процедурой, обеспечивающей его перемещение (потомок неперемещаемого отрезка) и т. д.
Полиморфизм — это свойство, которое позволяет одно и то же имя использовать для решения нескольких технически разных задач. Полиморфизм подразумевает такое определение методов в иерархии типов, при которых метод с одним именем может применяться к различным родственным объектам. Полиморфизм означает, что для различных родственных объектов можно задать единый класс действий. Пример: перемещение по экрану любой геометрической фигуры. Затем для каждого конкретного объекта составляется своя подпрограмма, выполняющая это действие непосредственно для данного объекта (естественно, что перемещение по экрану точки отличается от перемещения отрезка, а перемещение отрезка, в свою очередь, отличается от перемещения многоугольника и т. д.), причем все эти подпрограммы могут иметь одно и то же имя. Когда потребуется перемещать конкретную фигуру, будет выбрана из всего класса соответствующая подпрограмма.
Может показаться, что сочетание в одном объекте параметров и действий над ними является искусственным объединением. Однако окружающие нас объекты как раз и обладают таким свойством. Взять, например, компьютер. Он состоит из отдельных частей (процессор, монитор, клавиатура и т. д.) и характеризуется рядом параметров (емкость памяти, разрешающая способность дисплея, емкость жесткого диска и т. д.). Все это представляет собой данные рассматриваемого объекта. Кроме этого компьютер может выполнять или над ним можно совершать определенные действия (вставить дискету, поместить точку на экран и т. д.). Так что, действительно, объект — компьютер представляет собой сочетание параметров и действий над ними.
Таким образом, задаваемый объект позволяет локализовать в одном месте его свойства и сделать его в некотором смысле замкнутым по отношению к другим объектам и элементам программы, что, конечно, может в ряде случаев упростить его программирование.
ООП обладает рядом преимуществ при создании больших программ:
- использование более естественных с точки зрения повседневной практики понятий, простота введения новых понятий;
- некоторое сокращение размера программ за счет того, что повторяющиеся (наследуемые) свойства и действия можно не описывать многократно, как это делается при использовании подпрограмм; кроме того, использование динамических объектов позволяет более эффективно использовать оперативную память;
- возможность создания библиотеки объектов;
- сравнительно простая возможность внесения изменений в программу без изменения уже написанных частей, а в ряде случаев и без перекомпиляции этих написанных и уже скомпилированных частей, используя свойства наследования и полиморфизма;
- возможность написания подпрограмм с различными наборами формальных параметров, но имеющих одно и то же имя, используя свойство полиморфизма;
- более четкая локализация свойств и поведения объекта в одном месте (используется свойство инкапсуляции), позволяющая проще разбираться со структурой программы, отлаживать ее, находить ошибки;
- возможность разделения доступа к различным объектам программы и т. д.
Однако ООП обладает и рядом недостатков и эффективно не во всех случаях:
- как правило, использование ООП приводит к уменьшению быстродействия программы;
- неэффективно ООП применительно к небольшим программам, поэтому его можно рекомендовать при создании больших программ, а лучше даже класса программ (например, создание интерактивных программ с использованием Turbo Vision, где основой является ООП);
- можно сказать, что ООП скорее не упрощает саму программу, а упрощает технологию ее создания.
Понятие объекта
Инкапсуляция. Для описания объектов зарезервировано слово Object. Тип Object — это структура данных, которая содержит поля и методы. Описание объектного типа выглядит следующим образом:
Туре <идентификатор типа o6ъекта> = Object <поле>; … <поле>; <метод>; … <метод>; End;
Поле содержит имя и тип данных. Методы — это процедуры или функции, объявленные внутри декларации объектного типа, в том числе и особые процедуры, создающие и уничтожающие объекты (конструкторы и деструкторы). Объявление метода внутри описания объектного типа состоит только из заголовка (как в разделе Interface в модуле).
Пример: опишем объект «обыкновенная дробь» с методами «НОД числителя и знаменателя», «сокращение», «натуральная степень».
Type Natur = l..32767; Frac=Record P: Integer; Q: Natur; End; Drob=Object A: Frac; Procedure NOD (Var C: Natur); Procedure Sokr; Procedure Stepen (N: Natur; Var C: Frac); End;
Описание объектного типа выражает такое свойство, как инкапсуляция (объединение).
Выделим некоторые особенности инкапсуляции:
- Реализация методов осуществляется в разделе описаний после объявления объекта. При реализации метода достаточно указать его заголовок без списка параметров, но с указанием объектного типа, методом которого он является.
- Все действия над объектом выполняются только с помощью его методов.
- Для работы с отдельным экземпляром объектного типа в разделе описания переменных должна быть объявлена переменная (или переменные) соответствующего типа.
Пример работы с описанным объектом, реализация его методов и обращение к ним приведен на стр.155-156 учебника И.Г. Семакин, А.П. Шестаков «Основы программирования».
Наследование. Объектные типы можно выстроить в иерархию. Один объектный тип может наследовать компоненты из другого объектного типа. Наследующий объект называется потомком. Объект, которому наследуют — предком. Если предок сам является чьим-либо наследником, то потомок наследует и эти поля и методы. Следует подчеркнуть, что наследование относится только к типам, но не экземплярам объекта.
Описание типа-потомка имеет отличительную особенность:
<имя типа_потомка> = Object (<имя типа_предка>),
дальнейшая запись описания обычная.
Поля наследуются без какого-либо исключения. Поэтому, объявляя новые поля, необходимо следить за уникальностью их имен, иначе совпадение имени нового поля с именем наследуемого поля вызовет ошибку. На методы это правило не распространяется.
Пример приведен на стр.157-158 учебника И.Г. Семакин, А.П. Шестаков «Основы программирования».
Правила наследования:
- информационные поля и методы родительского типа наследуются всеми его типами-потомками независимо от числа промежуточных уровней иерархии (т.е. самый нижний будет наследовать все поля верхней иерархии);
- доступ к полям и методам родительских типов в рамках описания любых типов-потомков выполняется так, как будто бы они описаны в самом типе-потомке;
- ни в одном из типов-потомков не могут использоваться идентификаторы полей, совпадающие с идентификаторами полей какого-либо из родительских типов. Это правило относится и к идентификаторам формальных параметров, указанных в заголовках методов;
- тип-потомок может доопределить произвольное число собственных методов и информационных полей;
- любое изменение текста в родительском методе автоматически оказывает влияние на все методы порожденных типов-потомков, которые его вызывают;
- в противоположность информационным полям идентификаторы методов в типах-потомках могут совпадать с именами методов в родительских типах. При этом одноименный метод в типе-
потомке подавляет одноименный ему родительский, и в рамках типа-потомка при указании имени такого метода будет вызываться именно метод типа-потомка, а не родительский;
Вызов наследуемых методов осуществляется согласно следующим принципам:
- при вызове метода компилятор сначала ищет метод, имя которого определено внутри типа объекта;
- если в типе объекта не определен метод с указанным в операторе вызова именем, то компилятор в поисках метода с таким именем поднимается выше к непосредственному родительскому типу;
- если наследуемый метод найден и его адрес подставлен, то следует помнить, что вызываемый метод будет работать так, как он определен и компилирован для родительского типа, а не для
типа-потомка. Если этот наследуемый родительский тип вызывает еще и другие методы, то вызываться будут только родительские или вышележащие методы, так как вызовы методов из нижележащих по иерархии типов не допускаются.
Полиморфизм (многообразие) предполагает определение класса или нескольких классов методов для родственных объектных типов так, что каждому классу отводится своя функциональная роль. Методы одного класса обычно наделяются общим именем.
Пример: необходимо поместить символ в элемент строки, строку или текст. Для этого можно включить соответствующие методы в типы tElLine (элемент строки), tLine (строка), tText (текст). Естественно, что эти действия будут отличаться в зависимости от того, куда помещается символ.
Если символ помещается в элемент строки, то необходимо знать только номер позиции, куда следует поместить символ.
Если символ помещается в строку, то сначала, исходя из координаты X в строке, следует определить, в какой конкретно элемент строки (получить указатель на этот элемент) и в какую позицию в этом элементе необходимо поместить символ. Затем уже разместить символ в соответствующем элементе.
Если символ следует поместить в текст, то сначала по координате Y следует определить строку (получить указатель на эту строку), а затем уже выполнить все действия, связанные с размещением символа в строке.
Таким образом, следует иметь три разные подпрограммы для трех различных типов. А т.к. все они выполняют одно и то же действие (размещают символ в соответствующем месте), было бы заманчиво назвать их одним именем. В языке Паскаль это делать запрещено. Для ООП в Турбо Паскале сделано исключение – эти подпрограммы могут иметь одно и то же имя.
В этой возможности – иметь несколько подпрограмм с одним и тем же именем – и заключается полиморфизм ООП. Вопрос, какая же конкретно подпрограмма будет использоваться в том или ином случае, определяется типом конкретного объекта, использующего эту подпрограмму.
Дополнительно пример можете рассмотреть на стр.159-161 учебника И.Г. Семакин, А.П. Шестаков «Основы программирования».
Принципы и основные понятия объектно-ориентированного программирования
Содержание:
- Использование типа object
- Инкапсуляция
- Наследование
- Полиморфизм
В основе объектно-ориентированного программирования (ООП) лежит понятие класса, под которым понимают новый тип данных, вводимый разработчиком при решении конкретной задачи. Элементами класса являются поля и методы. Поля — это данные, которые образуют значение нового типа данных. Методы — это операции над значениями нового типа данных.
Например, при решении задачи продажи билетов на самолет удобно ввести класс «Рейс самолета». Полями этого класса могут быть номер рейса, дата выполнения, аэропорты вылета и назначения, время вылета, время прибытия, тип самолета, общее количество мест, количество проданных и забронированных мест, цена одного билета. Методами создаваемого класса станут действия по продаже или бронированию заданного количества билетов, получению времени полета, получению количества свободных мест и т.п.
Технология ООП базируется на трех принципах: инкапсуляции, наследовании и полиморфизме. Первый принцип ООП — инкапсуляция — подразумевает такое объединение внутри класса его полей и методов, при котором доступ к полю возможен только путем вызова соответствующего метода. Реализация данного принципа позволяет существенно повысить надежность программирования, так как вместо практически неконтролируемого использования в программе имен переменных программист должен будет использовать для доступа к данным ограниченное число методов. Эти методы могут быть тщательно протестированы и, следовательно, с высокой вероятностью свободны от ошибок.
Элементы класса разделяются на личные, доступ к которым разрешен только внутри методов этого класса, и общие, доступ к которым разрешен в любом месте программы. Очевидно, что выполнение принципа инкапсуляции не позволяет создавать в классе общие поля.
Для использования в программе определенного класса необходимо создать его экземпляры (или объекты), являющиеся аналогами переменных обычных типов данных. Экземпляры класса в программе могут создаваться автоматически (при входе в соответствующий блок, в котором данные экземпляры были определены) или динамически (в произвольном месте программы с помощью специальной операции). Уничтожение автоматически созданных экземпляров класса происходит также автоматически при завершении выполнения блока программы, в котором они были определены. Уничтожение динамически созданных экземпляров класса может выполняться в любом месте программы с помощью явно указываемой специальной операции.
Среди методов класса существуют две специальные группы: конструкторы и деструкторы. Конструкторы выполняют функцию инициализации полей класса и должны вызываться при создании его экземпляров. Деструктор выполняется при уничтожении экземпляра класса.
Вызов метода класса отличается от вызова обычной функции (подпрограммы). Метод вызывается с указанием конкретного экземпляра класса, для которого он выполняется. Аналогично мы не можем в программе записать только знак операции +, а должны указать данные, над которыми выполняется сложение, например: a+1.
Интересным является вопрос полноты и избыточности набора методов создаваемого класса. Если набор методов окажется неполным, то программисту потребуется создать дополнительный программный код для реализации отсутствующих в используемом классе возможностей. Избыточность набора методов ухудшит показатели эффективности создаваемой программы. Хотя универсальных рекомендаций по этому поводу дать невозможно, обычно считается необходимым и достаточным, чтобы в наборе методов создаваемого класса присутствовали:
- конструкторы без параметров;
- конструкторы, имеющие параметры, или модификаторы (методы, изменяющие значения экземпляров класса);
- наблюдатели (методы, возвращающие значения, типы которых отличны от создаваемого класса).
От обычных элементов класса отличаются статические поля и методы. Статическое поле является общим для всех экземпляров класса (существует в единственной копии независимо от того, сколько экземпляров создано и создан ли хотя бы один экземпляр). Примером статического поля может быть поле, в котором осуществляется подсчет числа существующих экземпляров класса. Фактически статические поля класса являются глобальными переменными, «заключенными» внутри класса.
Статический метод может вызываться, если ни одного экземпляра класса не создано. В этом случае при вызове статического метода необходимо указать имя класса. В статическом методе нельзя обращаться к нестатическим полям класса.
Второй принцип ООП — наследование — это возможность определения для базового класса (предка) иерархии производных классов (наследников), в каждом из которых доступны элементы базового класса (их описания становятся частью описания производного класса). Помимо личных и общих элементов в классе можно определить защищенные поля и методы, доступ к которым разрешен только в методах самого класса и всех его наследников. Разрешено присваивание экземпляру класса (указателю на него) значения экземпляра того же класса (указателя на него) или производного от этого класса.
Реализация принципа наследования позволяет существенно сократить объем нового программирования. Поскольку совершенно оригинальных программ приходится создавать не так уж и много, программист может «позаимствовать» из предыдущего проекта те классы, которые могут ему пригодиться в новом проекте.
Наследование может быть личным или общим. При личном наследовании общие и защищенные элементы класса-предка становятся личными в классе-наследнике и не могут использоваться при дальнейшем наследовании элементов уже нового класса. При общем наследовании общие и защищенные элементы базового класса остаются такими же и в производном классе.
Наследование может быть также единичным (когда предок один) и множественным (когда базовых классов более одного). При реализации множественного наследования может возникнуть следующая проблема. Допустим, что у класса A, имеющего защищенное поле ра, определен наследник — класс В. Пусть в программе будет определен еще и класс С, который станет наследником классов А и В. Проблема заключается в том, что поле ра в классе С будет наследоваться как напрямую из класса А, так и из класса В, что вызовет неоднозначность при компиляции программы. Выходом из этой ситуации является введение так называемого виртуального наследования, при котором в объекты производных классов включаются не сами наследуемые поля, а указатели на занимаемую ими область памяти.
Третий принцип ООП — полиморфизм — предполагает возможность определения единого по имени метода для всей иерархии производных классов, причем в каждом из них этот метод может реализовываться со своими особенностями. Реализация принципа полиморфизма гарантирует, что для любого экземпляра класса будут вызываться методы именно этого класса, а не одного из его предков. Этот принцип ООП позволяет программисту свободно изменять те методы класса-предка, которые должны иначе выполняться в классе-наследнике.
Полиморфизм реализуется путем введения виртуальных методов, которые отличаются от обычных тем, что адрес вызываемого метода определяется не при компиляции программы, а при ее выполнении (происходит так называемое позднее связывание). В любой экземпляр класса с виртуальными методами добавляется скрытое поле — указатель на таблицу виртуальных методов, в которой для каждого такого метода указывается адрес его реализации в данном классе.
Покажем на небольшом примере необходимость введения виртуальных методов. Допустим, мы создали в программе класс X, имеющий методы f1 и f2, причем в методе f1 вызывается метод f2. Далее мы определили класс Y — наследник класса X — и изменили в Y метод f2. Пусть оу — экземпляр класса Y, для которого вызывается метод f1. Компилятор не находит в классе Y метод f1 и обращается к классу X, где метод с таким именем существует. Когда компилятор дойдет в методе f1 до вызова метода f2, то вызовет метод класса X, хотя объект оу относится к классу Y. При использовании в нашем примере обычных (невиртуальных) методов компилятор не сможет «узнать» о факте изменения в наследнике одного из методов класса-предка.
Конструкторы, за редким исключением, не должны объявляться виртуальными, так как они вызываются при создании экземпляра известного компилятору класса. А вот при уничтожении динамически созданного объекта и вызове деструктора конкретный класс адресуемого с помощью указателя объекта (базовый или производный) компилятору может быть и неизвестен. Поэтому для корректного уничтожения экземпляров классов разумно использовать виртуальные деструкторы.
В базовом классе можно описать только прототипы виртуальных методов без их полного определения, предполагая, что это обязательно будет сделано в классе-наследнике. Подобные методы называются абстрактными, а класс, имеющий хотя бы один абстрактный метод, — абстрактным классом. Создать экземпляры абстрактного класса нельзя.
Абстрактными обычно бывают старшие (расположенные на верхних уровнях иерархии) классы библиотеки классов. Например, абстрактный класс «Фигура» может содержать абстрактные методы «Показать» и «Переместить», которые будут реализованы в его наследниках — классах «Окружность», «Прямоугольник» и «Текст». Очевидно, что абстрактные методы должны быть виртуальными. В обычных методах абстрактного класса разрешен вызов абстрактных методов. Исключением здесь являются деструкторы абстрактных классов. В них вызов абстрактных методов запрещен, так как на момент этого вызова часть объекта, относящаяся к производному классу, уже может быть разрушена деструктором класса-наследника.
Разработка программы с использованием ООП начинается с разработки объектной модели проектируемой системы, для чего выполняется разбиение («декомпозиция») проблемы на объекты и выявление зависимостей между ними.
Использование типа object
Программа, написанная с использованием ООП, состоит из о6ъектов, которые могут взаимодействовать между собой. Программная реализация объекта представляет собой объединение данных и процедур их обработки. В Турбо Паскале имеется тип object, который можно считать обобщением структурного типа record. Переменные объектного типа называются экземплярами объекта.
В отличие от типа «запись» объектный тип содержит не только поля, описывающие данные, но также процедуры и функции, описания которых содержатся в описании объекта. Эти процедуры и функции и являются методами. Методам объекта доступны его поля. Методы и их параметры определяются в описании объекта, а их реализация дается вне этого описания, в том месте программы, которое предшествует вызову данного метода. В описании объекта содержаться лишь шаблоны обращений к методам, которые необходимы компилятору для проверки соответствия количества параметров и их типов при обращении к методам. Вот пример описания объекта:
type Location = object X,Y: Integer; procedure Init(InitX, InitY: Integer); function GetX: Integer; function GetY: Integer; end;
Объект описывается с помощью зарезервированных слов object…end, между которыми находятся описания полей и методов. В приведенном примере объект содержит два поля для хранения значений графических координат, а также описания процедуры и двух функций – методов данного объекта. В отличие от других описаний описание объектного типа может находиться только на самом верхнем уровне программной единицы (программы, модуля), в которой используется этот тип. В разделе описаний процедур, функций, методов такое описание содержаться не может.
Полное описание методов, то есть описание их реализации, должно находится после описания объекта. Имена методов составные и складываются из имени объекта и имени метода, разделенных точкой:
procedure Location.Init(nitX, InitY : Integer); begin X := InitX; Y := InitY; end; function Location.GetX : Integer; begin GetX := X; end; function Location.GetY : Integer; begin GetY := Y; end;
Инкапсуляция
Инкапсуляция заключается в том, что объект скрывает в себе детали, которые несущественны для его использования. Инкапсуляция является средством организации доступа к данным только через соответствующие методы.
В нашем примере описания объекта процедура инициализации Init и функции GetX и GetY уже не существует как отдельные, самостоятельные объекты. Это неотъемлемые части объектного типа Location. Если в программе имеется описание нескольких переменных указанного типа, то для храпения данных каждой переменной резервируется своя собственная область памяти, а указатели на точки входа в процедуру и функции — общие. Вызов каждого метода возможен только с помощью составного имени, явно указывающего, для обработки каких данных предназначен данный метод.
Наследование
Наследование позволяет определять новые объекты, используя свойства прежних, дополняя или изменяя их. Объект-наследник получает все поля и методы «родителя», к которым он может добавить свои собственные поля и методы или заменить родительские методы своими. Вот пример описания объекта-наследника:
type Point = object(Location) Visible : Boolean; procedure Init(nitX, InltY : Integer); procedure Show; procedure Hide; function IsVisible : Boolean; procedure MoveTo(NewX, NewY : Integer); end;
Наследником здесь является объект Point, а родителем — объект Location. Наследник не содержит описания полей и методов родителя. Имя последнего указывается в круглых скобках после слова object. Из методов наследника можно вызывать методы родителя. Для создания наследника не требуется иметь исходный текст объекта-родителя. Объект-родитель может быть в составе уже оттранслированного модуля.
Полиморфизм
Полиморфизм проявляется в том, что метод действует по-разному, в зависимости от свойств объекта. В возможности иметь несколько подпрограмм с одним и тем же именем и заключается полиморфизм ООП. Вопрос, какая же конкретно подпрограмма будет использоваться в том или ином случае, определяется типом конкретного объекта, использующего эту подпрограмму.
Контрольные вопросы:
1. Что лежит в основе ООП?
2. Что называется объектом ООП?
3. Что понимают под методами объекта?
4. Что называют интерфейсом объекта?
5. Перечислите основные свойства ООП и дайте им краткую характеристику.
6. Назовите преимущества и недостатки ООП.
7. По какому принципу осуществляется работа со стандартными модулями?