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

Использование классов и объектных интерфейсов

Указанные в разделе 3.1.1 недостатки записей (record) с точки зрения построения модуля библиотеки формальных средств моделирования могут быть преодолены при помощи классов и объектных интерфейсов [7]. Использование этих механизмов обеспечивает возможность наследования и применения виртуальных методов, делает код легко расширяемым при совместной разработке и использовании. Так, например, появляется возможность объявить отдельный класс симметричных матриц, обладающий тем же интерфейсом, что и обобщенный класс матриц. С точки зрения клиента работа с экземплярами такого класса ничем не будет отличаться от обычных матриц (кроме вызова конструктора при создании, где нужно явно указывать класс порождаемого экземпляра). В рамках класса симметричных матриц можно определить специфический метод присваивания значений элементам (выполняющий присваивание одновременно соответствующим элементам над и под главной диагональю). Также может быть целесообразно использование в этом классе альтернативной реализации функции вычисления обратных матриц (например, методом Холецкого), обеспечивающей более высокую точность и быстродействие.

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

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

На рис. 10 приведена упрощенная диаграмма классов, показывающая отношения между сущностями модуля.

Рис. 1

Ниже приведён пример объявления интерфейсов вектора (IVector) и матрицы (IMatrix) и классов, содержащих реализацию этих интерфейсов (TVector, TMatrix и TSymmetricMatrix).

Листинг 4.

Interface

// Объявление типов

Type

// Опережающая декларация интерфейса матрицы

IMatrix = Interface;

// Интерфейс вектора

IVector = Interface

// Функция получения значения элемента

function GetItem(i: Integer): Extended;

// Процедура установки значения элемента

procedure SetItem(i: Integer; value: Extended);

// Функция получения размера вектора

function GetSize: Integer;

// Функция получения индекса последнего элемента

function GetHigh: Integer;

// Функция создания копии объекта

function Clone: IVector;

// Процедура изменения размера вектора

procedure Resize(n: Integer);

// Функция сложения векторов

function Add(a: IVector): IVector;

// Функция вычитания векторов

function Sub(a: IVector): IVector;

// Функция умножения вектора на число

function Mult(a: Extended): IVector; overload;

// Функция умножения вектора на матрицу

function Mult(a: IMatrix): IVector; overload;

// Функция скалярного произведения векторов

function Mult(a: IVector): Extended; overload;

// Функция векторного произведения векторов

function Cross(a: IVector): IVector;

// Функция получения модуля вектора

function Length: Extended;

// Процедура нормирования вектора

procedure Norm;

// Свойство для чтения и изменения размера вектора

property Size: Integer read GetSize write Resize;

// Свойство для чтения индекса последнего элемента

property High: Integer read GetHigh;

// Свойство для доступа к элементам вектора

property Item[i: Integer]: Extended read GetItem write SetItem; default;

end;

// Интерфейс матрицы

IMatrix = Interface

// Функция получения значения элемента

function GetItem(i,j: Integer): Extended;

// Процедура установки значение элемента

procedure SetItem(i,j: Integer; value: Extended);

// Функция получения кол-ва строк

function GetRowCount: Integer;

// Функция получения кол-ва столбцов

function GetColCount: Integer;

// Функция получения индекса последней строки

function GetRowHigh: Integer;

// Функция получения индекса последнего столбца

function GetColHigh: Integer;

// Функция создания копии объекта

function Clone: IMatrix;

// Процедура изменения размерности

procedure Resize(n, m: Integer);

// Функция сложения матриц

function Add(a: IMatrix): IMatrix;

// Функция вычитания матриц

function Sub(a: IMatrix): IMatrix;

// Функция умножения матрицы на матрицу

function Mult(a: IMatrix): IMatrix; overload;

// Функция умножения матрицы на число

function Mult(a: Extended): IMatrix; overload;

// Функция умножения матрицы на вектор

function Mult(a: IVector): IVector; overload;

// Функция вычисления определителя

function Det: Extended;

// Функция обращения матрицы

function Inv: IMatrix;

// Функция транспонирования матрицы

function T: IMatrix;

// Процедура перестановки строк

procedure SwapRows(i, j: Integer);

// Свойство для достуа к элементам матрицы

property Item[i,j: Integer]: Extended read GetItem write SetItem; default;

// Свойство для чтения количества строк

property RowCount: Integer read GetRowCount;

// Свойство для чтения количества столбцов

property ColCount: Integer read GetColCount;

// Свойство для чтения индекса последней строки

property RowHigh: Integer read GetRowHigh;

// Свойство для чтения индекса последнего столбца

property ColHigh: Integer read GetColHigh;

end;

// Объявление класса векторов

TVector = class (TInterfacedObject, IVector)

Protected

data: array of Extended;

Public

// Конструктор по умолчанию

constructor Create; overload;

// Конструктор по количеству элементов

constructor Create(n: Integer); overload;

// Конструктор на основе обычного массива

constructor Create(a: array of Extended); overload;

// Деструктор

destructor Destroy;

// Функция получения значения элемента

function GetItem(i: Integer): Extended;

// Процедура установки значения элемента

procedure SetItem(i: Integer; value: Extended);

// Функция получения размера вектора

function GetSize: Integer;

// Функция получения индекса последнего элемента

function GetHigh: Integer;

// Функция создания копии объекта

function Clone: IVector;

// Функция изменения размера вектора

procedure Resize(n: Integer);

// Функция сложения векторов

function Add(a: IVector): IVector;

// Функция вычитания векторов

function Sub(a: IVector): IVector;

// Функция умножения вектора на число

function Mult(a: Extended): IVector; overload;

// Функция умножения вектора на матрицу

function Mult(a: IMatrix): IVector; overload;

// Функция скалярного произведения векторов

function Mult(a: IVector): Extended; overload;

// Функция векторного произведения векторов

function Cross(a: IVector): IVector;

// Функция получения модуля вектора

function Length: Extended;

// Процедура нормирования вектора

procedure Norm;

// Свойство для чтения и изменения размера вектора

property Size: Integer read GetSize write Resize;

// Свойство для чтения индекса последнего элемента

property High: Integer read GetHigh;

// Свойство для доступа к элементам вектора

property Item[i: Integer]: Extended read GetItem write SetItem; default;

end;

// Объявление класса матриц

TMatrix = class (TInterfacedObject, IMatrix)

Protected

data: array of array of Extended;

Public

// Конструктор по умолчанию

constructor Create; overload;

// Конструктор по количеству элементов

constructor Create(n, m: Integer); overload;

// Деструктор

destructor Destroy;

// Функция получения значения элемента

function GetItem(i,j: Integer): Extended;

// Процедура установки значение элемента

procedure SetItem(i,j: Integer; value: Extended);

// Функция получения кол-ва строк

function GetRowCount: Integer;

// Функция получения кол-ва столбцов

function GetColCount: Integer;

// Функция получения индекса последней строки

function GetRowHigh: Integer;

// Функция получения индекса последнего столбца

function GetColHigh: Integer;

// Функция создания копии объекта

function Clone: IMatrix;

// Функция изменения размерности

procedure Resize(n, m: Integer);

// Функция сложения матриц

function Add(a: IMatrix): IMatrix;

// Функция вычитания матриц

function Sub(a: IMatrix): IMatrix;

// Функция умножения матрицы на матрицу

function Mult(a: IMatrix): IMatrix; overload;

// Функция умножения матрицы на число

function Mult(a: Extended): IMatrix; overload;

// Функция умножения матрицы на вектор

function Mult(a: IVector): IVector; overload;

// Функция вычисления определителя

function Det: Extended;

// Функция обращения матрицы

function Inv: IMatrix;

// Функция транспонирования матрицы

function T: IMatrix;

// Процедура перестановки строк

procedure SwapRows(i, j: Integer);

// Порождающая функция для формирования единичной матрицы

class function E(n: Integer): IMatrix; static;

// Свойство для достуа к элементам матрицы

function Item[i,j: Integer]: Extended read GetItem write SetItem; default;

// Свойство для чтения количества строк

property RowCount: Integer read GetRowCount;

// Свойство для чтения количества столбцов

property ColCount: Integer read GetColCount;

// Свойство для чтения индекса последней строки

property RowHigh: Integer read GetRowHigh;

// Свойство для чтения индекса последнего столбца

property ColHigh: Integer read GetColHigh;

end;

// Объявление класса симметричных матриц

TSymmetricMatrix = class (TMatrix)

Public

// Процедура установки значения элемента

procedure SetItem(i,j: Integer; value: Extended);

end;

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

Реализация методов в основном аналогична описанной в предыдущем разделе, но имеет некоторые отличия в деталях, в частности, если результат вычисления функции имеет тип IVector, IMatrix, IQuaternion, то перед обращением к объекту результата его необходимо явно создать с использованием конструктора, а не просто вызвать метод изменения размерности (как в случае использования структур):

Result:= TVector.Create(…);

Дело в том, что все поля объекта некоторого класса размещаются в динамической памяти, которая выделяется только при вызове конструктора, а поля экземпляра структуры размещаются на стеке, они всегда доступны для использования в рамках той области видимости, в которой эти экземпляры объявлены. Конечно, перед обращением к данным динамического массива (поле data), который имеется в составе как динамического объекта, так и структуры, необходимо выделить для него (массива) память с использованием системной процедуры SetLength. Это выполняется в процедуре Resize, которая имеет одинаковую реализацию в обоих вариантах.

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

Листинг 5.

function TVector.Clone: IVector;

Begin

Result:= TVector.Create;

(Result as TVector).data:= self.data;

Result.Resize(self.Size);

end;

Реализация подобного метода в классе TMatrix выглядит аналогично (за исключением типа результата и вызова метода Resize с двумя аргументами), а в классе TQuaternion может иметь вид:

Листинг 6.

function TQuaternion.Clone: IQuaternion;

Begin

Result:= TQuaternion.Create;

with Result as TQuaternion do

Begin

q0:= self.q0;

Q:= self.Q.Clone;

end;

end;

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

Листинг 7.

Var

// Объявление переменных типа матрица и вектор

A, B, C: IMatrix;

X, Y, Z: IVector;

Begin

// Создание динамических объектов матриц с указанием их размера

A:= TMatrix.Create(3, 3);

B:= TMatrix.Create(3, 3);

// Инициализация элементов матриц

A[0,0]:= 1; A[0,1]:= 2; …

B[0,0]:= 10; A[0,1]:= 20; …

// Сложение матриц А и В

C:= A.Add(B);

// Обращение матрицы А

B:= A.inv;

// Умножение матриц А и В

A:= A.Mult(B);

// Создание динамического объекта вектора

X:= TVector.Create(3);

// Инициализация элементов вектора

X[0]:= 1; X[1]:= 2; X[2]:= 3;

// Создание и инициализация объекта вектора массивом чисел

Y:= TVector.Create([4.1, 5, 6.9]);

// Умножение матрицы на вектор и вектора на матрицу

Z:= C.Mult(X);

Z:= X.Mult(C);

end;

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

3.2. Примеры реализации ПМО на С++

В языке программирования С++ операции с динамическими массивами выглядят несколько сложнее, чем в Delphi, так как здесь отсутствуют встроенные механизмы выделения и удаления памяти для размещения многомерных массивов, изменения количества элементов в массиве и определения объёма ранее выделенной памяти. Однако, эти проблемы можно решить штатными средствами, используя операторы new (для выделения памяти), delete[] (для её удаления), функцию копирования заданной области памяти и дополнительные переменные для хранения информации о размерах массивов. При этом естественной необходимостью становится применение объектно-ориентированного подхода для обеспечения инкапсуляции рутинных процедур работы с памятью и повышения удобства использования процедур векторной, матричной и кватернионной алгебр.

В С++ отсутствует понятие интерфейса, однако те цели, для которых интерфейс использовался в реализации аналогичной библиотеки классов в Delphi (см. раздел 3.1.2), т.е. автоматическое управление памятью, можно достичь здесь иным способом. Наиболее распространённый подход для этого – использование «обёртки» динамического объекта в виде стекового. При этом подходе объект, содержащий указатель на динамически выделяемую память, создаётся статически (на стеке) в той области видимости, в которой он объявлен. При создании объекта вызывается конструктор, на который можно возложить задачу выделения динамической памяти и инициализацию соответствующего указателя внутри этого объекта. При выходе из области видимости автоматически будет вызван деструктор, где следует предусмотреть освобождение ранее выделенной памяти. Таким образом, комбинация автоматических (стековых) объектов с динамически выделяемой памятью внутри них позволяет решить проблему «утечек» памяти без необходимости её ручной очистки после завершения использования объекта вектора, матрицы или кватерниона.

Следует отметить, что в современных версиях языка С++, начиная с С++11, введены конструкции, реализующие механизм так называемых «умных указателей» для контроля выделения и очистки памяти. Подробнее о них можно узнать, например, из [9]. Но и при «ручном» управлении памятью на основе вышеописанного подхода можно построить надёжное и компактное ПМО, пример реализации которого будет рассмотрен далее.

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

Поделиться:





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



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