Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

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




Рассмотрим проблемы реализации проекта, разработанного с использованием методологии OMT, в системах программирования ООЯ. В качестве примеров таких систем программирования будут рассмотрены системы программирования известных ООЯ C++, Eiffel и Smalltalk.

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

Рис. 5.1. Часть объектной модели графического редактора

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

5.3. Реализация на C++

C++ является наиболее распространенным ООЯ программирования. Поэтому рассмотрение вопросов реализации начнем с него. Поскольку C++ хорошо известен и достаточно широко распространен (это один из самых популярных языков программирования), здесь приводится лишь краткий обзор некоторых свойств и конструкций этого языка.

5.3.1. Реализация классов. Реализация прикладной ПС, спроектированной с помощью ОО методологии (например, методологии 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;

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

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 обеспечивается автоматическая сборка мусора, что существенно упрощает программирование).

5.3.3. Вызов операций. В 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;

5.3.4. Использование наследования. В 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++ поддерживается множественное наследование: каждый класс может иметь один или несколько суперклассов.

5.3.5. Реализация зависимостей. Зависимости в 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*); //определить количество объектов в наборе };

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

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

5.3.6. Шаблоны в C++. В C++ возможно и параметрическое программирование (программирование с использованием родовых компонентов). Родовые (параметризованные) компоненты обладают свойством адаптивности к конкретной ситуации, в которой такой компонент используется, что позволяет разрабатывать достаточно универсальные и в то же время высокоэффективные компоненты программ (в частности, объекты).

Параметрическое программирование в C++ реализовано с помощью шаблонов (template). В C++ определено два вида шаблонов: шаблоны-классы и шаблоны-функции.

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

Возможность параметрического программирования на C++ обеспечивается стандартной библиотекой шаблонов STL (Standard Template Library). Она включена в последнюю предварительную версию Стандарта C++. Библиотека подробно описана в книге D.R.Musser, A.Saini "STL Tutorial and Reference Guide. C++ Programming with the Standard Template Library" Addison-Wesley, 1996 (книга доступна также по http://www.aw.com/ cp/musser-saini.html). Документация по текущей версии STL доступна по http://weber.u.washington.edu/~bytewave/bytewave_stl.html или http://www.ualberta.ca/~nyu/ stl/stl.html. Наконец, реализация текущей версии STL доступна по ftp://ftp.cs.rpi.edu/pub/stl.

Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...