Объектноориентированное программирование
Скачать 1.73 Mb.
|
Министерство образования и науки Российской Федерации Южно-Уральский государственный университет Кафедра системного программирования 004.4(07) Р159 Г.И. Радченко, Е.А. Захаров ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ Конспект лекций Челябинск Издательский центр ЮУрГУ 2013 УДК 004.4(075.8) Р159 Одобрено учебно-методической комиссией факультета вычислительной математики и информатики. Конспект лекций подготовлен в соответствии с ФГОС ВПО 3-го поколе- ния по образовательным направлениям 010300.62 «Фундаментальная ин- форматика и информационные технологии» и 010400.62 «Прикладная ма- тематика и информатика». Рецензенты: доктор физ.-мат. наук, профессор В.И. Ухоботов, кандидат технических наук А.В. Созыкин Р159 Радченко, Г.И. Объектно-ориентированное программирование / Г.И. Рад- ченко, Е.А. Захаров. Челябинск: Издательский центр ЮУрГУ, 2013. 167 с. В учебном пособии представлены основы применения объектно- ориентированного программирования. Рассмотрены основные концеп- ции объектно-ориентированного программирования на примере языка программирования С++. Рассматриваются понятия класса и объекта, концепция наследования, шаблоны функций и классов, методы пере- грузки операторов, методы обработки исключительных ситуаций. При- водится обзор основных порождающих, структурных и поведенческих паттернов проектирования. Рассматриваются особенности использова- ния стандартной библиотеки шаблонов (STL) в С++. Пособие предназначено для студентов бакалавриата по направле- нию 010300 «Фундаментальная информатика и информационные тех- нологии» при изучении курса «Объектно-ориентированное програми- рование», а также для обучения студентов бакалавриата по направле- нию 010400 «Прикладная математика и информатика». УДК 004.4(075.8) © Издательский центр ЮУрГУ, 2013 3 1. ВВЕДЕНИЕ 1.1 Сложность разработки программного обеспечения Мы окружены сложными системами: персональный компьютер; любое дерево, цветок, животное; любая материя – от атома до звезд и галактик; общественные институты – корпорации и сообщества. Большинство сложных систем обладает иерархической структурой. Но не все ПО – сложное. Существует класс приложений которые проектиру- ются разрабатываются и используются одним и тем же человеком. Но они имеют ограниченную область применения. Вопросы сложности появляются при разработке корпоративного ПО, промышленного программирования. Сложность ПО вызывается четырьмя основными причинами: сложностью реальной предметной области, из которой исходит заказ на разработку; трудностью управления проектированием; необходимостью обеспечить достаточную гибкость программы; сложность описания поведения больших дискретных систем. 1.2 Декомпозиция Декомпозиция один из способов борьбы со сложностью. Рис. 1. Пример алгоритмической декомпозиции 4 Необходимо разделять систему на независимые подсистемы, каждую из которых разрабатывать отдельно. Выделяют следующие методы декомпо- зиции: алгоритмическая декомпозиция (см. рис. 1); объектно-ориентированная декомпозиция (см. рис. 2). 1.3 Краткая история языков программирования Выделяют следующие этапы развития языков программирования высо- кого уровня: Языки первого поколения (1954 1958) FORTRAN 1 Математические формулы ALGOL-58 Математические формулы Языки второго поколения (1959 1961) FORTRAN II Подпрограммы ALGOL-60 Блочная структура, типы данных COBOL Описание данных, работа с файлами LISP Обработка списков, указатели, сборка мусора Языки третьего поколения (1962 1970) PL/I FORTRAN+ALGOL+COBOL Pascal Простой наследник ALGOL-60 Simula Классы, абстракция данных Разрыв преемственности (1970 1980) Рис. 2. Пример объектно-ориентированной декомпозиции 5 C Эффективный высокоуровневый язык FORTRAN 77 Блочная структура, типы данных Бум ООП (1980 1990) Smalltalk 80 Чисто объектно-ориентированный язык C++ С + Simula Ada83 Строгая типизация; сильное влияние Pascal Появление инфраструктур (1990 …) Java Блочная структура, типы данных Python Объектно-ориентированный язык сценариев Visual C# Конкурент языка Java для среды Microsoft .NET 1-е поколение (рис. 3) преимущественно использовалось для научных и технических вычислений, математический словарь. Языки освобождали от сложностей ассемблера, что позволяло отступать от технических деталей реализации компьютеров. Программы, написанные на языках программирования первого поколе- ния, имеют относительно простую структуру, состоящую только из глобаль- ных данных и подпрограмм. Языки 2-го поколения (рис. 4) сделали акцент на алгоритмических аб- стракциях, что приблизило разработчиков к предметной области. Появилась процедурная абстракция, которая позволила описать абстрактные про- граммные функции в виде подпрограмм. На 3-е поколение языков программирования (рис. 5) влияние оказало то, что стоимость аппаратного обеспечения резко упала, при этом, производи- тельность экспоненциально росла. Языки поддерживали абстракцию дан- ных и появилась возможность описывать свои собственные типы данных Рис. 3. Топология языков первого поколения 6 (структуры). В 1970-е годы было создано несколько тысяч языков програм- мирования для решения конкретных задач, но практически все из них ис- чезли. Осталось только несколько известных сейчас языков, которые про- шли проверку временем. В модули собирали подпрограммы, которые будут изменяться сов- местно, но их не рассматривали как новую технику абстракции. В 1980-е произошел бум развития объектно-ориентированного програм- мирования (рис. 6). Языки данного времени лучше всего поддерживают объ- ектно-ориентированную декомпозицию ПО. В 90-х появились инфраструк- туры (J2EE, .NET), предоставляющие огромные объемы интегрированных сервисов. Рис. 4. Топология языков второго поколения Рис. 5. Топология языков третьего поколения 7 Основным элементом конструкции служит модуль, составленный из ло- гически связанных классов и объектов, а не подпрограмма. 1.4 Объектно-ориентированное программирование Объектно-ориентированное программирование – это методология про- граммирования, основанная на представлении программы в виде совокуп- ности объектов, каждый из которых является экземпляром определенного класса, а классы образуют иерархию наследования. Объект – это нечто, имеющее четко определенные границы. Однако, этого недостаточно, чтобы отделить один объект от другого или дать оценку качества абстракции. Объект обладает состоянием, поведением и идентич- ностью; структура и поведение схожих объектов определяет общий для них класс; термины «экземпляр класса» и «объект» взаимозаменяемы. Класс – это множество объектов, обладающих общей структурой, пове- дением и семантикой. Отдельный объект – это экземпляр класса. Класс представляет лишь абстракцию существенных свойств объекта. Состояние объекта характеризуется перечнем (обычно статическим) всех свойств данного объекта и текущими (обычно динамическими) значе- ниями каждого из этих свойств. Например: торговый автомат имеет свой- Рис. 6. Топология объектно-ориентированного программирования 8 ство: способность принимать монеты; этому свойству соответствует дина- мическое значение – количество принятых монет. Пример описания состо- яния объекта: struct PersonnelRecord { char name[100]; int socialSecurityNumber; char department[10]; float salary; }; Поведение объекта – это то, как объект действует и реагирует; поведение выражается в терминах состояния объекта и передачи сообщений. Опера- цией называется определенное воздействие одного объекта на другой с це- лью вызвать соответствующую реакцию. Например, клиент может активи- зировать операции append() и pop() для того, чтобы управлять объектом- очередью: class Queue { public: Queue(); Queue(const Queue&); virtual Queue(); virtual Queue& operator=(const Queue&); virtual int operator==(const Queue&) const; int operator!=(const Queue&) const; virtual void clear(); virtual void append(const void*); virtual void remove(int at); virtual int length() const; virtual int isEmpty() const; }; Индивидуальность объекта – это такое свойство объекта, которое отли- чает его от всех других объектов. В большинстве языков программирования при создании объект именуется, поэтому многие путают адресуемость и ин- дивидуальность. Невозможность отличить имя объекта от самого объекта является источником множества ошибок в ООП. 9 2. ОБЪЕКТНО - ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ . ЯЗЫК С++ Объектно-ориентированное программирование строится на трех осново- полагающих принципах: инкапсуляция, полиморфизм и наследование. 2.1 Инкапсуляция Инкапсуляция – это процесс отделения друг от друга элементов объекта, определяющих его устройство и поведение; инкапсуляция служит для того, чтобы изолировать контрактные обязательства абстракции от их реализа- ции. Пусть члену класса требуется защита от «несанкционированного до- ступа». Как разумно ограничить множество функций, которым такой член будет доступен? Очевидный ответ для языков, поддерживающих объектно- ориентированное программирование, таков: доступ имеют все операции, которые определены для этого объекта, иными словами, все функции- члены. Например: class window { // ... protected: Rectangle inside; // ... }; class dumb_terminal: public window { // ... public: void prompt(); // ... }; Здесь в базовом классе window член inside типа Rectangle описыва- ется как защищенный ( protected ), но функции-члены производных клас- сов, например, dumb_terminal::prompt() , могут обратиться к нему и вы- яснить, с какого вида окном они работают. Для всех других функций член window::inside недоступен. В таком подходе сочетается высокая степень защищенности с гибко- стью, необходимой для программ, которые создают классы и используют их. 10 Неочевидное следствие из этого: нельзя составить полный и окончатель- ный список всех функций, которым будет доступен защищенный член, по- скольку всегда можно добавить еще одну, определив ее как функцию-член в новом производном классе. Для метода абстракции данных такой подход часто бывает мало приемлемым. Если язык ориентируется на метод абстрак- ции данных, то очевидное для него решение – это требование указывать в описании класса список всех функций, которым нужен доступ к члену. В С++ для этой цели используется описание частных ( private ) членов. Важность инкапсуляции, т.е. заключения членов в защитную оболочку, резко возрастает с ростом размеров программы и увеличивающимся разбро- сом областей приложения. 2.2 Наследование Наследование представляет собой способность производить новый класс из существующего базового класса. Производный класс – это новый класс, а базовый класс – существующий класс. Когда вы порождаете один класс из другого (базового класса), производный класс наследует элементы базового класса. Для порождения класса из базового начинайте определение произ- водного класса ключевым словом class , за которым следует имя класса, двоеточие и имя базового класса, например class dalmatian: dog Когда вы порождаете класс из базового класса, производный класс мо- жет обращаться к общим элементам базового класса, как будто эти эле- менты определены внутри самого производного класса. Для доступа к част- ным данным базового класса производный класс должен использовать ин- терфейсные функции базового класса. Внутри конструктора производного класса ваша программа должна вы- звать конструктор базового класса, указывая двоеточие, имя конструктора базового класса и соответствующие параметры сразу же после заголовка конструктора производного класса. Чтобы обеспечить производным классам прямой доступ к определенным элементам базового класса, в то же время защищая эти элементы от остав- шейся части программы, C++ обеспечивает защищенные ( protected ) эле- менты класса. Производный класс может обращаться к защищенным эле- ментам базового класса, как будто они являются общими. Однако для остав- шейся части программы защищенные элементы эквивалентны частным. Если в производном и базовом классе есть элементы с одинаковым име- нем, то внутри функций производного класса C++ будет использовать эле- 11 менты производного класса. Если функциям производного класса необхо- димо обратиться к элементу базового класса, вы должны использовать опе- ратор глобального разрешения, например base class:: member 2.3 Полиморфизм Полиморфный объект представляет собой такой объект, который может изменять форму во время выполнения программы. В объектно-ориентированных языках класс является абстрактным типом данных. Полиморфизм реализуется с помощью наследования классов и вир- туальных функций. Класс-потомок наследует сигнатуры методов класса-ро- дителя, а реализация, в результате переопределения метода, этих методов может быть другой, соответствующей специфике класса-потомка. Другие функции могут работать с объектом класса-родителя, но при этом вместо него во время исполнения будет подставляться один из классов-потомков. Это называется поздним связыванием. Класс-потомок сам может быть родителем. Это позволяет строить слож- ные схемы наследования – древовидные или сетевидные. Абстрактные (или чисто виртуальные) методы не имеют реализации во- обще. Они специально предназначены для наследования. Их реализация должна быть определена в классах-потомках. Класс может наследовать функциональность от нескольких классов. Это называется множественным наследованием. Множественное наследование создаёт проблему (когда класс наследуется от нескольких классов-посред- ников, которые в свою очередь наследуются от одного класса (так называе- мая «Проблема ромба»): если метод общего предка был переопределён в по- средниках, неизвестно, какую реализацию метода должен наследовать об- щий потомок. Решается эта проблема через виртуальное наследование. 2.4 История появления C++ В 1980 году Бьерн Страуструп в AT&T Bell Labs стал разрабатывать рас- ширение языка С под условным названием C++. Стиль ведения разработки вполне соответствовал духу, в котором создавался и сам язык С, – в него вводились те или иные возможности с целью сделать более удобной работу конкретных людей и групп. Первый коммерческий транслятор нового языка, получившего название C++ появился в 1983 году. Он представлял со- бой препроцессор, транслировавший программу в код на С. Однако факти- 12 ческим рождением языка можно считать выход в 1985 году книги Стра- уструпа. Именно с этого момента C++ начинает набирать всемирную попу- лярность. Главное нововведение C++ механизм классов, дающий возможность определять и использовать новые типы данных. Программист описывает внутреннее представление объекта класса и набор функций-методов для до- ступа к этому представлению. Одной из заветных целей при создании C++ было стремление увеличить процент повторного использования уже напи- санного кода. Концепция классов предлагала для этого механизм наследо- вания. Наследование позволяет создавать новые (производные) классы с расширенным представлением и модифицированными методами, не затра- гивая при этом скомпилированный код исходных (базовых) классов. Вместе с тем наследование обеспечивает один из механизмов реализации полимор- физма базовой концепции объектно-ориентированного программирования, согласно которой, для выполнения однотипной обработки разных типов данных может использоваться один и тот же код. Собственно, полиморфизм тоже один из методов обеспечения повторного использования кода. Введение классов не исчерпывает всех новаций языка C++. В нем реали- зованы полноценный механизм структурной обработки исключений, отсут- ствие которого в С значительно затрудняло написание надежных программ, механизм шаблонов – изощренный механизм макрогенерации, глубоко встроенный в язык, открывающий еще один путь к повторной используемо- сти кода, и многое другое. Таким образом, генеральная линия развития языка была направлена на расширение его возможностей путем введения новых высокоуровневых конструкций при сохранении сколь возможно полной совместимости с ANSI С. Конечно, борьба за повышение уровня языка шла и на втором фронте: те же классы позволяют при грамотном подходе упрятывать низко- уровневые операции, так что программист фактически перестает непосред- ственно работать с памятью и системно-зависимыми сущностями. На данный момент существуют следующие стандарты языка С++: ANSI C++ / ISO-C++ – 1996 год, ISO/IEC 14882:1998 – 1998 год, ISO/IEC 14882:2003 – 2003 год, C++/CLI – 2005 год, TR1 – 2005 год, C++11 – 2011 год. 13 2.5 Нововведения и отличия C++ от C В C++ появились классы и объекты. Технически, класс C++ – это тип структуры в C, а объект – переменная такого типа. Разница только в том, что в C++ есть еще модификаторы доступа и полями могут быть не только дан- ные, но и функции (функции-методы). В C++ появились две новые операции: new и delete . В первую очередь это – сокращения для распространенных вызовов функций malloc и free : При вызове new автоматически вызывается конструктор, а при вызове delete – деструктор. Так что нововведение можно описать формулой: new = malloc + конструктор, delete = free + деструктор. В C++ появились функции, которые вызываются автоматически после создания переменной структуры (конструкторы) и перед ее уничтожением (деструкторы). Во всех остальных отношениях это – обычные функции, на которые наложен ряд ограничений. Некоторые из этих ограничений ничем не оправданы и мешают: например, конструктор нельзя вызвать напрямую (деструктор, к счастью, можно). Нельзя вернуть из конструктора или де- структора значение. Что особенно неприятно для конструктора. А деструк- тору нельзя задать параметры. При программировании на C часто бывает так, что имеется несколько вариантов одной и той же структуры, для которых есть аналогичные функ- ции. Например, есть структура, описывающая точку ( Point ) и структура, описывающая окружность ( Circle ). Для них обоих часто приходится вы- полнять операцию рисования ( Point ). Так что, если у нас есть блок данных, где перемешаны точки, окружности и прочие графические примитивы, то перед нами стоит задача быстро вызвать для каждого из них свою функцию рисования. Обычное решение – построить таблицу соответствия «вариант струк- туры – функция». Затем берется очередной примитив, определяется его тип, и по таблице вызывается нужная функция. В C++ всем этим занимается ком- пилятор: достаточно обозначить функцию-метод как virtual , и для всех одноименных функций будет создана таблица и поле типа, за которыми сле- дить будет также компилятор. При попытке вызвать функцию с таким име- нем, будет вызвана одна из серии одноименных функций в зависимости от типа структуры. Исключение по своей сути – это последовательность goto и return . Ос- новано на C-технологии setjmp / longjmp try и catch – это setjmp с про- веркой. throw – это longjmp . Когда вызывается throw , то проверяется: если 14 он окажется внутри блока try , то выполняется goto на парный блок catch Если нет, то делается return и ищется catch на уровень выше и так далее. |