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

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




Прикладную ПС, спроектированную по методологии OMT, совсем не обязательно реализовывать на ООЯ. Рассмотрим, как ОО проект можно реализовать на C (как известно, он не является ОО). Проще всего это сделать, отобразив ОО конструкции на C (для ООЯ такое отображение автоматически реализуется компилятором). Реализация состоит в выполнении следующих шагов:

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

· Обеспечить передачу параметров методам.

· Реализовать наследование.

· Обеспечить механизм выбора нужного метода.

· Реализовать зависимости.

· Реализовать синхронизацию параллельных процессов.

· Обеспечить упрятывание (инкапсуляцию) внутренних деталей реализации классов.

Рассмотрим, как можно выполнить перечисленные шаги при реализации на C, на примере реализации графического редактора (см. п. 5.2).

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

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

Например, класс Window можно представить как следующую структуру (struct) в C:

struct Window { /* структура соответствует классу class Window для которого определены следующие методы *(все методы имеют дополнительный параметр, Window* this, определяющий экземпляр * структуры (объект), к которому применяется метод): * public: * Window(Length x0, Length y0, Length width, Length height, * Window* this); * D_Window (Window* this); -- соответствует деструктору ~Window() * void add_box (Length x, Length y, Length width, Length height, * Window* this); * void add_circle (Length x, Length y, Length radius, Window* this); * void clear_selections (Window* this); * void cut_selections (Window* this); * Group* group_selections (Window* this); * void move_selections (Length deltax, Length deltay, Window* this); * void redraw_all (Window* this); * void select_item (Length x, Length y, Window* this); * void ungroup_selections (Window* this); * private: * void add_to_selections (Shape* shape, Window* this); */ Length xmin; Length ymin; Length xmax; Length ymax; }; При этом предполагается, что тип Length введен через typedef floast Length; Ссылки на объекты класса Window реализуются как указатели в C: struct Window* window; Length x1 = window->xmin;

Объект может быть при этом размещен статически, автоматически (в стеке), или динамически (в куче).

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

add_to_selections (shape, self) struct Window* self; struct Shape* shape;

5.5.3. Размещение объектов в памяти. Объекты, определенные в программе на C могут размещаться в памяти программы (статические глобальные объекты), в стеке (автоматически размещаемые локальные объекты), или в куче (динамические объекты). Время жизни статических объектов - это все время выполнения программы. Они используются для хранения глобальных переменных и констант, но при ОО разработке программ стараются использовать как можно меньше глобальных объектов, так как они нарушают модульную структуру программы. Глобальные объекты в C объявляются на внешнем уровне программного файла, вне составляющих его функций; инициализация таких объектов обычно производится при их порождении во время компиляции программы. Пример объявления статического (глобального) объекта:

static struct Window outer_window = {0.0, 0.0, 8.5, 11.0};

При вызове методов, использующих объявленную глобальную переменную, им следует передавать ее адрес (&outer_window).

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

Динамически размещаемые объекты необходимо использовать, когда во время компиляции программы неизвестно их количество. Такие объекты размещаются в куче по запросу (функции malloc или сalloc) во время выполнения программы. Будучи размещенным в памяти динамический объект сохраняется в ней до тех пор, пока не будет явным образом отменен (функция free). Пример функции размещения и инициализации динамического объекта:

struct Window* create_window(xmin, ymin, width, height); Length xmin, ymin, width, height; { struct Window* window; /*размещение объекта*/ window = (struct Window*)malloc(sizeof(struct Window)); /*инициализация объекта*/ window-> xmin = xmin; window-> ymin = ymin; window-> xmax = xmin + width; window-> ymax = ymin + height; return window; };

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

Наследование в C реализуются через указатели. Рассмотрим, например, конкретные подклассы Box и Circle абстрактного класса Shape. На C их можно представить следующим образом:

struct Shape { struct ShapeClass* class; Length x; Length y; }; struct Box { struct BoxClass* class; Length x; Length y; Length width; Length height; }; struct Circle { struct CircleClass* class; Length x; Length y; Length radius; };

Указатель на структуры Box или Circle можно передать функции, ожидающей указатель на Shape, так как первые несколько членов структур Box и Circle идентичны первым членам структуры Shape (это позволяет привести тип указателя на структуру Box или Circle привести к типу указателя на структуру Shape).

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

Методы, выбор которых возможен во время компиляции, на C могут быть реализованы как непосредственные вызовы соответствующих функций. Это прежде всего относится к тем операциям, которые реализуются только одним методом, что снимает проблему выбора. Например, все методы в классе Window уникальны (единственны). Однако наиболее общий подход состоит в определении для каждого класса дескриптора, содержащего указатели на все методы для каждой операции, видимой из этого класса, включая унаследованные операции. Каждый дескриптор класса является структурой C, элементами которой являются все операции, определенные в этом классе или унаследованные им от суперкласса. Следующий фрагмент программы содержит определения дескрипторов класса для классов Item, Shape, Box и Circle:

struct ItemClass { char* class_name; void(* cut)(); void(* move)(); Boolean(* pick)(); void(* ungroup)(); }; struct ShapeClass { char* class_name; void(* cut)(); void(* move)(); Boolean(* pick)(); void(* ungroup)(); void(* write)(); }; struct BoxClass { char* class_name; void(* cut)(); void(* move)(); Boolean(* pick)(); void(* ungroup)(); void(* write)(); }; struct CircleClass { char* class_name; void(* cut)(); void(* move)(); Boolean(* pick)(); void(* ungroup)(); void(* write)(); };

В дескрипторе класса определены операции, видимые в классе, но необходимость определить и проинициализировать дескрипторы класса для каждого класса: в каждом поле дескриптора класса должно стоять имя функции, которая реализует метод, определенный в этом классе или унаследованный им (например, класс Box наследует операцию move у класса Shape, но заменяет операции pick и write своими собственными методами:

struct BoxClass BoxClass = { "Box", Shape__cut, Shape__move, Box__pick, Shape__ungroup, Box__write, }; struct CircleClass CircleClass = { "Circle", Shape__cut, Shape__move, Circle__pick, Shape__ungroup, Circle__write, };

Если у класса есть атрибуты уровня класса, их тоже можно запомнить в дополнительных полях дескриптора класса. Например, можно поместить в дескриптор класса имя этого класса и использовать его во время отладки, или каким-либо другим образом.

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

struct Circle * create_circle(x0,y0,radius0) Length x0,y0,radius0; { struct Circle * new_circle; new_circle = (struct Circle*)malloc(sizeof(struct Circle)); new_circle->class = &CircleClass; new_circle->x = x0; new_circle->y = y0; new_circle-> radius = radius0; return new_circle; };

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

struct Shape* shape; Length x,y; Boolean status; status = (*shape->class->pick)(shape,x,y);

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

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

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

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

В качестве примера первого способа реализации зависимостей рассмотрим реализацию зависимости (типа "много к одному") между классами Item и Group:

struct Item { struct ItemClass* class; struct Group* group; }; struct Group { struct GroupClass* class; int item_count; struct Item** items; };

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

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

COMMON/WINDOW/XMIN,YMIN,XMAX,YMAX,WINDOW REAL XMIN(1000),YMIN(1000),XMAX(1000),YMAX(1000),WINDOW(1000) INTEGER NWINDOW

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

В Фортране нет средств определения новых типов данных, поэтому нет возможности определить тип Length: данные этого типа должны иметь один из определенных в Фортране типов (INTEGER, REAL, COMPLEX, LOGICAL и CHARACTER).

В Стандарте Фортрана ограничена длина идентификаторов, однако большая часть компиляторов поддерживают идентификаторы в 32 и более символов. В примерах фрапгментов фортранных программ предполагается, что допустимы достаточно длинные идентификаторы, которые могут содержать символ подчеркивания ("_"). Если компилятор не поддерживает длинных имен, все идентификаторы из примеров следует заменить более короткими, что, естественно, ухудшит читаемость программ.

Таким образом, в объектной программе на Фортране каждый объект некоторого класса может быть представлен индексом атрибутных массивов этого класса, причем указанный индекс определяет доступ к атрибутам этого объекта:

INTEGER AWINDOW REAL X1 X1 = XMIN(AWINDOW)

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

SOUBROUTINE WINDOW__ADD_TO_SELECTIONS (SELF, SHAPE) INTEGER SELF, SHAPE

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

FUNCTION CIRCLE__PICK(X0, Y0, RADIUS, X, Y) LOGICAL CIRCLE__PICK REAL X0, Y0, RADIUS, X, Y

Размещение объектов в памяти. Обычно размещение новых объектов производится в заранее определенные массивы. Если требуется динамическое распределение памяти, необходимо самому смоделировать управление кучей в своей программе:

FUNCTION CREATE_WINDOW(X1, Y1, WIDTH, HEIGHT) COMMON/WINDOW/XMIN,YMIN,XMAX,YMAX,WINDOW REAL XMIN(1000),YMIN(1000),XMAX(1000),YMAX(1000),WINDOW(1000) INTEGER NWINDOW INTEGER CREATE_WINDOW REAL X1, Y1, X2, Y2 NWINDOW = NWINDOW + 1 XMIN(NWINDOW) = X1 YMIN(NWINDOW) = Y1 XMAX NWINDOW) = X1 + WIDTH YMAX NWINDOW) = Y1 + HEIGHT CREATE_WINDOW = NWINDOW RETURN END

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

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

COMMON/SHAPE/XMIN,YMIN,XMAX,YMAX,WINDOW,RADIUS,NSHAPE REAL XMIN(1000),YMIN(1000),XMAX(1000),YMAX(1000),WINDOW(1000) REAL RADIUS(1000) INTEGER NSHAPE

Второй способ более экономен. Он состоит в представлении класса как набора подклассов, каждый из которых реализован как самостоятельный класс со своим набором массивов и индексами объектов. Каждый класс представляется парой целых массивов: один из этих массивов содержит код подкласса, другой - индексы объектов в соответствующем массиве подкласса. В следующем примере определен класс ITEM, который имеет подклассы SHAPE (содержит не более 1000 объектов) и GROUP (содержит не более 100 объектов). Общий блок CLASSES определяет целочисленный код для каждого класса.

COMMON/ITEM/ITEM_CLASS,ITEM_ID,NITEM INTEGER ITEM_CLASS(1100),ITEM_ID(1100) INTEGER NITEM/0/ COMMON/CLASSES/GROUP,BOX,CIRCLE INTEGER GROUP/1/,BOX/2/,CIRCLE/3/

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

FUNCTION CREATE_CIRCLE(X0,Y0,RADIUS0) COMMON/WINDOW/XMIN,YMIN,XMAX,YMAX,WINDOW сюда следует поместить описания общих блоков ITEM, SHAPE и CLASSES INTEGER CREATE_CIRCLE NSHAPE = NSHAPE + 1 X(NSHAPE) = X0 Y(NSHAPE) = Y0 RADIUS(NSHAPE) = RADIUS0 NITEM = NITEM + 1 ITEM_CLASS(NITEM) = CIRCLE ITEM_ID(NITEM) = NSHAPE CREATE_CIRCLE = NITEM RETURN END

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

FUNCTION PICK(CLASS,ID,PX,PY) LOGICAL PICK LOGICAL GROUP_PICK,BOX_PICK,CIRCLE_PICK INTEGER CLASS,ID GOTO(100,200,300) CLASS PICK =.FALSE. RETURN 100 PICK = GROUP_PICK(ID,PX,PY) RETURN 200 PICK = BOX_PICK(ID,PX,PY) RETURN 300 PICK = CIRCLE_PICK(ID,PX,PY) RETURN END

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

Целесообразность использования ОО систем программирования связана со следующими обстоятельствами:

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

· Удобство: в не ОО системе программирования программист вынужден вручную отслеживать иерархию классов при вызове методов и передаче им параметров; при изменениях в иерархии классов, он должен вручную внести соответствующие изменения в программу.

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

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

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

Поделиться:





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



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