Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции по технологии программирования / Объектно-ориентированный стиль.doc
Скачиваний:
77
Добавлен:
02.05.2014
Размер:
79.87 Кб
Скачать

Понятия ооп

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

Переменная тип процедура вызов процедуры объект класс метод сообщение

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

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

Основными свойствами ООП являются инкапсуляция, наследование и полиморфизм.

Под инкапсуляцией понимается сокрытие данных и операций АТД от внешних программ, использующих их.

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

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

Сокрытие данных обеспечивается ключевым словом class и ключевыми словами, связанными с правами доступа: public, private и protected. Класс - это способ инкапсуляции типов данных и связанных с ними функций. Разрешение частной (private) и общей (public) видимости для членов класса дает программисту возможность управлять тем, какие части структуры данных будут модифицируемы. Private части скрыты от кода пользователя, a public - доступны. Члены класса со статусом доступа protected занимают промежуточное положение: они доступны как внутри класса, так и во всех производных от него классах.

Определение класса схематически описывается в виде

Class Имя_класса

{ Раздел атрибутов;

Раздел операций; }

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

По умолчанию все члены класса считаются закрытыми и доступ к ним могут получить только функции-члены этого класса. Если необходимо изменить тип доступа к членам класса, перед ними следует указать один из спецификаторов доступа public, protected или private.

Чтобы скрыть реализацию операций, определение класса разбивается на две части: интерфейс класса и реализацию класса. Интерфейс класса содержит только объявление его элементов: переменных и методов. Определения методов в виде их реализации отделены от интерфейса и помещаются в реализацию класса. Достоинство этой схемы в том, что программист может использовать класс, зная его интерфейс и не вдаваясь в детали реализации (вспомним один из принципов Хольта, где утверждалось, что модуль легче использовать, чем разработать! Для классов этот принцип справедлив вдвойне). Из интерфейса можно понять, какие в классе операции, как их вызывать, какова их семантика. Реализация операций может быть произвольной, лишь бы она соответствовала семантике. Изменение реализации при сохранении интерфейса и семантики не влияет на программы, использующие данный класс. Признаком хорошего стиля считается полное сокрытие данных (переменных класса) с помощью спецификаторов private и protected, означающее, что доступ к ним возможен только посредством методов данного класса или его наследников, а непосредственный доступ к ним запрещен.

Ниже приведен пример описания структуры (слева) и класса (справа) на языке СИ++. В нем создается тип данных (класс) degree. Закрытая переменная-член data_value доступна только функциям-членам класса. Одновременно создается и объект данного класса – переменная deg.

// trigon.cpp

// в программе создается структура, содержащая

// тригонометрические функции

#include <iostream.h>

#include <math.h>

const double DEG_TO_RAD = 0.0174532925;

struct degree { class degree {

double data_value; struct degree {

void set_value(double angle); double data_value;

public:

void set_value(double);

double get_sine(void); double get_sine(void);

double get_cosine(void); double get_cosine(void);

double get_tangent(void); double get_tangent(void);

double get_cotangent(void); double get_cotangent(void);

double get_secant(void); double get_secant(void);

double get_cosecant(void); double get_cosecant(void);

} deg; } deg;

void degree:: set_value(double angle) void degree:: set_value(double angle)

{ {

data_value = angle; data_value = angle;

} }

double degree::get_sine(void) double degree::get_sine(void)

{ {

return (sin(DEG_TO_RAD * data_value)); return (sin(DEG_TO_RAD * data_value));

} }

double degree::get_cosine(void) double degree::get_cosine(void)

{ {

return (cos(DEG_TO_RAD * data_value)); return (cos(DEG_TO_RAD * data_value));

} }

double degree::get_tangent(void) double degree::get_tangent(void)

{ {

return (tan(DEG_TO_RAD * data_value)); return (tan(DEG_TO_RAD * data_value));

} }

double degree::get_cotangent(void) double degree::get_cotangent(void)

{ {

return (1.0 / tan(DEG_TO_RAD * data_value)); return (1.0 / tan(DEG_TO_RAD * data_value));

} }

double degree::get_secant(void) double degree::get_secant(void)

{ {

return (1.0 / sin(DEG_TO_RAD * data_value)); return (1.0 / sin(DEG_TO_RAD * data_value));

} }

double degree::get_cosecant(void) double degree::get_cosecant(void)

{ {

return (1.0 / cos(DEG_TO_RAD * data_value)); return (1.0 / cos(DEG_TO_RAD * data_value));

} }

main ()

{

// устанавливаем угол = 25 градусов

get set_value(25.0);

cout << “ синус угла равен ” << deg.get_sine() cout << “ косеканс угла равен ” << deg.get_ cosecant ()

<< end1; << end1;

cout << “ косинус угла равен ” << deg.get_cosine() return(0);

<< end1; }

cout << “ таненс угла равен ” << deg.get_ tangent()

<< end1;

cout << “ котангенс угла равен” << deg.get_ cotangent ()

<< end1;

cout << “ секанс угла равен ” << deg.get_ secant ()

<< end1;

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

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

Рис.1

Отношение наследования обычно представляют в виде графа, узлы которого соответствуют классам, и из узла А в узел В ведет дуга, если класс В является непосредственным наследником класса А. Этот граф изображает иерархию классов с точки зрения наследования. Если наследование единичное (иерархическое), соответствующий класс является деревом. В корне дерева находится прародитель - класс, для которого все остальные классы являются потомками. На рис.1,а в виде графа представлена иерархия единичного наследования классов, а на рис.1,б представлен реальный пример – дерево каталогов диска С.

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

Производный класс расширяет свойства и поведение базового класса. В производном классе можно:

• объявить новые переменные с любыми правами доступа;

• объявить новые методы с любыми правами доступа;

• переопределить (перекрыть) методы базового класса.

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

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

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