Свойства ООП

ПРИМЕЧАНИЕ

Примером реализации событийно-управляемой модели может служить любая программа, управляемая с помощью меню. После запуска такая программа пассивно ожидает действий пользователя и должна уметь правильно отреагировать на любое из них. Событийная модель является противоположностью традиционной (директивной), когда код управляет данными: программа после старта предлагает пользователю выполнить некоторые действия (ввести данные, выбрать режим) в соответствии с жестко заданным алгоритмом.

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

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

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

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

Иерархия классов представляется в виде древовидной структуры, в которой более общие классы располагаются ближе к корню, а более специализированные - на ветвях и листьях. В С++ каждый класс может иметь сколько угодно потомков и предков. Иногда предки называются надклассами или суперклассами, а потомки - подклассами или субклассами.

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

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

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

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

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

Описание класса

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

Данные класса называются полями (по аналогии с полями структуры), а функции класса - методами. Поля и методы называются элементами класса. Описание класса в первом приближении выглядит так:

class <имя>{ [ private: ] <описание скрытых элементов> public: <описание доступных элементов>}; // Описание заканчивается точкой с запятой

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

Поля класса:

  1. могут быть простыми переменными любого типа, указателями, массивами и ссылками (т.е. могут иметь практически любой тип, кроме типа этого же класса, но могут быть указателями или ссылками на этот класс);
  2. могут быть константами (описаны с модификатором const), при этом они инициализируются только один раз (с помощью конструктора) и не могут изменяться;
  3. могут быть описаны с модификатором static, но не как auto, extern и register.

Инициализация полей при описании не допускается.

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

Локальные классы имеют некоторые особенности:

  • локальный класс не может иметь статических элементов;
  • внутри локального класса можно использовать из охватывающей его области типы, статические (static) и внешние (extern) переменные, внешние функции и элементы перечислений;
  • запрещается использовать автоматические переменные из охватывающей класс области;
  • методы локальных классов могут быть только встроенными (inline);
  • если один класс вложен в другой класс, они не имеют каких-либо особых прав доступа к элементам друг друга и могут обращаться к ним только по общим правилам.

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

class monster{int health, ammo;public: monster(int he = 100, int am = 10) { health = he; ammo = am;} void draw(int x, int y, int scale, int position); int get_health(){return health;} int get_ammo(){return ammo;}};

В этом классе два скрытых поля - health и ammo, получить значения которых извне можно с помощью методов get_health() и get_ammo(). Доступ к полям с помощью методов в данном случае кажется искусственным усложнением, но надо учитывать, что полями реальных классов могут быть сложные динамические структуры, и получение значений их элементов не так тривиально. Кроме того, очень важной является возможность вносить в эти структуры изменения, не затрагивая интерфейс класса.

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

В приведенном классе содержится три определения методов и одно объявление (метод draw). Если тело метода определено внутри класса, он является встроенным (inline). Как правило, встроенными делают короткие методы. Если внутри класса записано только объявление (заголовок) метода, сам метод должен быть определен в другом месте программы с помощью операции доступа к области видимости:

void monster::draw(int x, int y, int scale, int position){ /* тело метода */}

Встроенные методы можно определить и вне класса с помощью директивы inline (как и для обычных функций, она носит рекомендательный характер):

inline int monster::get_ammo(){return ammo;}

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

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

Типы данных struct и union являются специальными видами класса.