Объектно-ориентированное программирование учебное пособие



страница14/15
Дата17.04.2013
Размер1.64 Mb.
ТипУчебное пособие
1   ...   7   8   9   10   11   12   13   14   15

Реализация управления
Реализация управления связана с реализацией динамической модели объектов системы. Известны три подхода к реализации динамической модели:

  • процедурное управление: состоянию соответствует определенный фрагмент программы;

  • управление через события;

  • использование параллельных независимых задач.


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

  • Перестроить классы и операции.

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

  • Использовать делегирование операций, когда наследование семантически некорректно.


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

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

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

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

  • Операция может быть определена не во всех классах некоторой группы классов; можно, тем не менее, вынести ее в их общий суперкласс, переопределив ее в подклассах как пустую там, где она не нужна.


Использование делегирования операций можно пояснить на следующем примере (рисунок ).
Класс стек близок классу список, причем операциям стека push и pop соответствуют очевидные частные случаи операций списка add и remove. Если реализовать класс стек как подкласс класса список, то придется применять вместо операций push и pop более общие операции add и remove, следя за их параметрами, чтобы избежать записи или чтения из середины стека; это неудобно и чревато ошибками. Гораздо лучше объявить класс список телом класса стек (делегирование), обращаясь к операциям списка через операции стека. При этом, не меняя класса список, мы заменяем его интерфейс интерфейсом класса стек.






Рис. 5.16. Реализация стека с использованием наследования (а)

и делегирования (б)
Разработка зависимостей
Зависимости - это "клей" объектной модели: именно они позволяют рассматривать модель как нечто целое, а не просто как множество классов.

О
дносторонние зависимости можно реализовать с помощью ссылок (указателей) (см. рисунок ). При этом если кратность зависимости равна единице, ей соответствует один указатель, если кратность больше единицы, то множество указателей.
Рис. 5.17. Реализация односторонней зависимости

Н
а следующем рисунке показан способ реализации двусторонней зависимости с помощью указателей.
Рис. 5.18. Реализация двусторонней зависимости
Далее представлен способ реализации зависимости с помощью таблицы (как в реляционных базах данных).





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

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

Каждый язык программирования имеет средства для выражения трех сторон спецификации разрабатываемой прикладной системы: структур данных, потоков управления и функциональных преобразований. Далее будут рассмотрены проблемы, решаемые на этапе реализации объектно-ориентированного проекта, разработанного с использованием методологии OMT.
Объектно-ориентированный стиль программирования
Правильно разработанные программы должны не только удовлетворять своим функциональным требованиям, но и обладать такими свойствами, как:

  • повторная используемость;

  • расширяемость;

  • устойчивость к неправильным данным;

  • системность.

Правильный объектно-ориентированный стиль программирования обеспечивает наличие этих свойств. Поясним это на примере свойства системности.

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

Следующие рекомендации могут помочь разрабатывать классы, обладающие свойством системности.

  • Методы должны быть хорошо продуманы.

  • Методы должны быть понятны всем, кто их прочитает.

  • Методы должны быть легко читаемы.

  • Имена должны быть такими же, как и в модели.

  • Методы должны быть хорошо документированы.

  • Спецификации методов должны быть доступны.


Естественно, что к этим и им подобным "рекомендациям" следует относиться с известной долей юмора. Но выполнять их, тем не менее, полезно.
Объектно-ориентированные системы программирования
Р
ассмотрим проблемы реализации проекта, разработанного с использованием методологии OMT, в системах программирования объектно-ориентированных языков. В качестве примеров таких систем программирования будут рассмотрены системы программирования объектно-ориентированного языка C++.

Рис. 5.20

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

Изложение будет вестись на примере реализации графического редактора, часть объектной модели которого представлена на рисунке. Редактор поддерживает манипулирование рекурсивными группами графических объектов, составленных из прямоугольников и овалов (в частности, кругов); при этом определена операция "сгруппировать", которая превращает группу объектов в единый объект (все это очень похоже на графическую подсистему редактора Microsoft Word).

Язык C++ является наиболее распространенным объектно-ориентированным языком программирования. Далее приводится обзор свойств и конструкций этого языка. В настоящее время издано огромное количество учебных пособий, справочников и других руководств по языку C++. Одним из наиболее полных, изданных на русском языке, является книга Ирэ Пол. Объектно-ориентированное программирование с использованием C++. // DiaSoft Ltd., Киев -- 1995.
Реализация классов
Реализация прикладной программной системы, спроектированной с помощью объектно-ориентированной методологии (например, методологии OMT), на языке C++ начинается с определения классов, разработанных на этапе проектирования, на этом языке. При этом желательно сохранение имен и, по возможности, других обозначений, введенных на этапе проектирования. Рассмотрим в качестве примера, как реализовать на языке C++ класс Window, показанный на рисунке 5.1. Отметим, что реализация класса на языке C++ содержательно мало отличается от его представления в объектной модели OMT.
class Window

{

public:

// конструктор

Window (Length x0, Length y0, Length width, Length height);
// деструктор

~Window ();
// методы

void add_box (Length x, Length y, Length width, Length height);

void add_circle (Length x, Length y, Length radius);

void clear_selections ();

void cut_selections ();

Group* group_selections ();

void move_selections (Length deltax, Length deltay);

void redraw_all ();

void select_item (Length x, Length y);

void ungroup_selections ();
private:

Length xmin;

Length ymin;

Length xmax;

Length ymax;

void add_to_selections (Shape* shape);

};
В определении класса на языке C++ и атрибуты, и методы называются членами этого класса; их определения могут следовать в тексте определения класса в произвольном порядке. Члены класса могут быть общедоступными (public), или приватными (private); вне класса определен доступ только к его общедоступным членам, а приватные члены доступны только методам своего класса. В рассматриваемом примере все атрибуты являются приватными, а все методы (кроме метода add_to_selections) - общедоступными, так что прочитать или изменить значение каждого атрибута можно только с помощью соответствующего метода; это рекомендуемая, хотя и не обязательная дисциплина программирования на языке C++ (определение всех атрибутов класса как приватных называется инкапсуляцией данных).

Тип Length должен быть определен пользователем (обычно такие определения делаются в одном из файлов-заголовков, вставляемых в программу по #include). Для определения типа используется оператор typedef. Например:
typedef float Length;
или
typedef int Length;

Порождение объектов
В каждом классе языка C++ могут быть определены специальные методы (один, или несколько), обращение к которым приводит к порождению нового объекта этого класса. Такие методы называются конструкторами. Конструкторы могут иметь параметры, что позволяет определить начальное состояние объекта при его порождении. Конструкторы имеют то же имя, что и имя класса, в котором они определены, так что если класс имеет несколько конструкторов, то они должны различаться числом и типом своих параметров. В качестве примера рассмотрим конструктор класса Window:
Window::Window (Length x0, Length y0, Length width, Length height)

{

xmin = x0;

ymin = y0;

xmax = x0 + width;

ymax = y0 + height;

}
При обращении к этому конструктору порождается новое окно с координатами нижнего левого угла (x0,y0), шириной width и высотой height. Наряду с рассмотренным в этом классе могут быть определены и другие конструкторы:
Window (Length x0, Length y0);//берутся стандартные размеры окна
Window (); //берутся стандартные размеры и положение окна

В языке C++ поддерживается три вида выделения памяти для размещения объектов: в фиксированной глобальной памяти (статическая память, static), в системном стеке (автоматическая память, automatic), в "куче" (динамическая память, dynamtic).

Чтобы разместить объект в статической памяти достаточно либо объявить его вне какой-либо функции, либо при его объявлении указать ключевое слово static. Статическая память выделяется компилятором во время компиляции программы и не меняется во время ее выполнения. Конструктор можно использовать для инициализации объектов, размещаемых в статической памяти (для выделения статической памяти под объект без его инициализации достаточно просто объявить его). Объявление
Window main_window = Window(0.0, 0.0, 8.5, 11.0)

определяет статический объект main_window (основное окно), проинициализированный значениями параметров конструктора.

Локальные объекты, объявляемые внутри функций, размещаются в автоматической памяти (системном стеке). Их тоже можно инициализировать с помощью конструктора. Наконец, обращение к конструктору в операторе new в процессе выполнения программы порождает объект, размещаемый в динамической памяти. Например:
Window *window = new Window (0.0, 0.0, 8.5, 11.0);
При этом выражение new Window (0.0, 0.0, 8.5, 11.0) имеет своим значением указатель на порожденный динамический объект.

Для терминации объектов можно использовать специальный метод, являющийся одним из членов класса и называемый деструктором. Как и конструктор, он имеет то же имя, что и имя класса, в котором он определен, но первым символом имени деструктора является тильда (~):
Window::~Window ();

{

… //убрать окно и перекрасить освободившуюся область

}
Наконец, для освобождения динамической памяти от объектов, которые уже не нужны, используется операция delete, например:
delete window;
Необходимость использования операции delete связана с тем, что в системе C++ не производится сборки мусора, и программист должен сам заботиться об освобождении динамической памяти от ненужных объектов, чтобы избежать ее переполнения (в более современной системе программирования Java обеспечивается автоматическая сборка мусора, что существенно упрощает программирование).
Вызов операций
В языке C++ операция (метод) определяется как один из членов класса. При вызове операции используются те же обозначения, что и при обращении к атрибутам: операция выбора члена "->" применяется к соответствующему указателю:
Shape* shape;
shape->move(dx,dy);
Параметрами операции могут быть значения одного из встроенных типов (int, float, char и т.п.), либо значения типов, определенных с помощью typedef, либо объекты некоторых классов, либо указатели переменных и констант перечисленных типов, либо указатели объектов.

Имя атрибута или операции, используемое в качестве идентификатора в реализации метода неявно ссылается на соответствующие члены объекта, к которому применяется операция. В следующем примере x и y являются атрибутами объекта класса Shape, к которому будет применена операция move:
void Shape::move (Length deltax, Length deltay)
{

x = x + deltax;

y = y + deltay;

}
Это эквивалентно применению неявного параметра this, значением которого всегда является указатель объекта, к которому применяется операция. Следующий фрагмент программы эквивалентен предыдущему:
void Shape::move (Length deltax, Length deltay)
{

this->x = this->x + deltax;

this->y = this->y + deltay;

}
Ссылка на любой другой объект в описании операции должна обязательно быть квалифицированной (содержать указатель соответствующего объекта):
window->xmin = x1;
Использование наследования
В языке C++ наследование устанавливается только при составлении программы и не может быть изменено в процессе ее выполнения. Поведение каждого объекта полностью определяется классом этого объекта и одинаково для всех объектов данного класса. Все характеристики наследования одинаковы для всех объектов каждого класса.

Список суперклассов (если он не пуст) указывается в начале определения каждого класса; подкласс называется также производным классом. В следующем примере приведено описание класса Item, а также описания подкласса Shape класса Item и подклассов Box и Circle класса Shape:
class Item

{

public:

virtual void cut ();

virtual void move (Length deltax, Length deltay) = 0;

virtual Boolean pick (Length px, Length py) = 0;

virtual void ungroup () = 0;

};
class Shape: public Item

{

protected:

Length x;

Length y;
public:

void cut ();

void draw () {write (COLOR_FOREGROUND);};

void erase (); {write (COLOR_BACKGROUND);};

void move (Length deltax, Length deltay);

Boolean pick (Length px, Length py) = 0;

void ungroup () { };

virtual void write (Color color) = 0;

};
class Box: public Shape

{

protected:

Length width;

Length height;
public:

Box (Length x0, Length y0, Length width0, Length height0);

Boolean pick (Length px, Length py);

void write (Color color);

};
class Circle: public Shape

{

protected:

Length radius;
public:

Circle (Length x0, Length y0, Length radius0);

Boolean pick (Length px, Length py);

void write (Color color);

};
Члены суперкласса (атрибуты и операции) наследуются его подклассами (члены, определенные в суперклассе, имеются у всех его подклассов). Члены суперкласса, определенные в нем как private, недоступны для операций его подклассов; для операций подклассов доступны члены суперкласса, определенные в нем protected и как public. Методы, определенные в суперклассе, могут быть переопределены в (некоторых) его подклассах, если они определены как виртуальные (virtual). Например, метод write класса Shape может быть переопределен в его подклассах Box и Circle, поэтому он определен как виртуальный; методы и в подклассах не переопределяются, поэтому их можно не объявлять как виртуальные. Если в определении виртуального метода указана его "инициализация" к 0 (virtual void write (Color color) = 0;), то он обязательно должен быть переопределен в каждом его подклассе (такой метод называется абстрактным). Класс, содержащий хотя бы один абстрактный метод, также называется абстрактным. Для абстрактных классов нельзя порождать объекты (объекты определены только для его подклассов). Если все методы класса определены как абстрактные, то говорят, что такой (абстрактный) класс определяет интерфейс, реализуемый в его подклассах.

В языке C++ поддерживается множественное наследование: каждый класс может иметь один или несколько суперклассов.
Реализация зависимостей
Зависимости в языке C++ реализуются с помощью указателей или с помощью специальных объектов. Например, зависимость "много-к-одному" между классами Item и Group реализована через указатели:
class Item{

public:

virtual void cut ();

virtual void move (Length deltax, Length deltay) = 0;

virtual Boolean pick (Length px, Length py) = 0;

virtual void ungroup () = 0;
private:

Group* group;

friend Group::add_item (Item*);

friend Group::remove_item (Item*);
public:

Group* get_group () {return group;};

};
class Group: public Item

{

public:

void cut ();

void move (Length deltax, Length deltay);

Boolean pick (Length px, Length py) = 0;

void ungroup () { };
private:

ItemSet* items;
public:

void add_item (Item*);

void remove_item (Item*);

ItemSet* get_items () {return items;}

};
Каждый раз, когда к зависимости добавляется (или из нее удаляется) связь, оба указателя должны быть изменены:
void Group::add_item (Item* item)

{

item->group = this;

items->add (item);

}

void Group::remove_item (Item* item);

{

item->group = 0;

items->remove (item);

}

Методы Group::add_item и Group::remove_item могут изменять приватные (private) атрибуты класса Item, хотя они определены в его подклассе Group, так как они определены как дружественные (friends) для класса Item.

В рассмотренном примере опущены проверки:

  1. не является ли включаемый в группу графический объект уже членом этой группы: в этом случае его незачем еще раз включать в группу;

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

Иногда связанные между собой (зависимые) объекты включают в так называемые коллективные классы. В качестве примера такого класса рассмотрим класс ItemSet (набор объектов):
class ItemSet

{

public:

ItemSet(); //создать пустой набор объектов

~ItemSet(); //уничтожить набор объектов

void add(Item*); //включить объект в набор

void remove(Item*); //исключить объект из набора

Boolean includes(Item*); //проверить наличие объекта в наборе

int size(Item*); //определить количество объектов в наборе

};
Коллективные классы часто используются в библиотеках классов. При работе с коллективными классами удобно использовать итераторы, т.е. объекты, с помощью которых можно "просмотреть" коллектив.

Зависимости между классами можно реализовать и с помощью специальных классов (каждый объект такого класса описывает связь между объектами соответствующих классов). В этом случае атрибуты класса соответствуют атрибутам описываемой им зависимости.
Дружественные функции
Дружественные функции – один из аспектов видимости в языке С++. Они представляют собой функции, которые описаны с модификатором friend в определении класса. Дружественным функциям разрешается читать и записывать поля данных объекта, описанные и как private, и как protected.

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

В объектно-ориентированных языках программирования полиморфизм – естественное следствие следующих особенностей:

  • механизма пересылки сообщений;

  • наследования;

  • принципа подстановки.

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

Чистый полиморфизм имеет место, когда одна и та же функция применяется к аргументам различных типов. В случае чистого полиморфизма есть одна функция (тело кода) и несколько ее интерпретаций. Другая крайность наблюдается, когда имеется множество различных функций (то есть тел кода с одним и тем же именем). Такая ситуация называется перегрузкой или “полиморфизмом ad hoc” .

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

В языке С++ полиморфные переменные возникают при использовании указателей или ссылок.

(Для понимания дальнейшего материала нужны некоторые пояснения: будем использовать понятие статический тип для обозначения типа, присвоенного переменной при ее описании. Термин динамический тип характеризует тип фактического значения. Тот факт что статический и динамический тип не обязаны совпадать, является одним из главнейших достоинств объектно-ориентированного программирования.)

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

Чтобы понять этот процесс, рассмотрим следующие два класса:
class One

{

public:

virtual int value( )

{

return 1;

}

};
1   ...   7   8   9   10   11   12   13   14   15

Похожие:

Объектно-ориентированное программирование учебное пособие icon13. Основные принципы Объектно-Ориентированного Программирования (ооп) Объектно-Ориентированное Программирование
Объектно-Ориентированное Программирование это методология программирования, которая основана на представлении программы в виде совокупности...
Объектно-ориентированное программирование учебное пособие iconОбъектно-ориентированное программирование на языке Delphi
Методическое пособие предназначено для изучения основ объектно-ориентированного языка программирования Delphi (ооп) без ориентации...
Объектно-ориентированное программирование учебное пособие iconОбъектно-ориентированное программирование

Объектно-ориентированное программирование учебное пособие iconВизуальное объектно-ориентированное программирование. Графический интерфейс: форма и управляющие элементы

Объектно-ориентированное программирование учебное пособие iconСоздание web-сайтов, разработка программного обеспечения, объектно-ориентированное программирование, проектирование баз данных

Объектно-ориентированное программирование учебное пособие iconИнтегрированная среда разработки языка Visual Basic
...
Объектно-ориентированное программирование учебное пособие iconКурс: Объектно-ориентированное программирование
Напишите программу для общения через Internet. Программа должна состоять из двух частей: сервер и клиент. Сервер стартует в качестве...
Объектно-ориентированное программирование учебное пособие iconПрограммирование простых типов данных
Учебное пособие предназначено для студентов физического факультета, изучающих курс "Программирование и вычислительная физика"
Объектно-ориентированное программирование учебное пособие iconЗадание к лабораторным работам по курсу “Объектно-ориентированное программирование”
Разработать структуру элементов данных класса в виде динамической структуры данных (динамический массив, список, массив указателей)....
Объектно-ориентированное программирование учебное пособие iconУчебное пособие Уфа 2006 удк 519. 8 Б 19 ббк 22. 1: 22. 18 (Я7)
Бакусова С. М. Математика. Часть Математическое программирование / Учебное пособие. Уфа: ООО полиграфстудия «Оптима», 2006. – 71...
Разместите кнопку на своём сайте:
ru.convdocs.org


База данных защищена авторским правом ©ru.convdocs.org 2016
обратиться к администрации
ru.convdocs.org