- •Ооп: Лекция 9. Композиция объектов.
- •Композиция объектов
- •Простейшая композиция по значению
- •Множественная композиция по значению
- •Ссылочная композиция — логическое объединение
- •Композиция с разрываемой связью
- •Разрываемая ссылочная композиция
- •Недетерминированная множественность композиции
- •Множественная композиция объектов-сущностей
- •Полные примеры из лекции
Недетерминированная множественность композиции
До сих пор был рассмотрен лишь случай, когда множественная композиция имела конечную детерминированную кратность. В частности, в примере о многоугольнике (Polygon) количество дочерних объектов в явном виде передавалось родительскому объекту-агрегату в конструкторе. Это число использовалось для выделения памяти массива дочерних объектов. Разумеется, массив всегда имеет конечный фиксированный размер. Изменить размер массива дочерних объектов в течение жизни объекта-агрегата было нельзя.
На практике при моделировании реальных иерархий взаимодействующих объектов из естественных предметных областей довольно редко встречаются случаи, когда число связей при множественной композиции является заранее известным и неизменным. Гораздо чаще встречаются ситуации, когда количество дочерних объектов варьируется от 0 до бесконечности, а состав связей динамически изменяется за время жизни объекта-агрегата.
Предположим, местоположение вертолета в пространстве в течение вылета протоколируется с некоторой периодичностью, и затем путь подвергается той или иной форме обработки. Пусть для целей протоколирования используется следующий класс:
flightjournal.hpp
#ifndef _FLIGHTJOURNAL_HPP_
#define _FLIGHTJOURNAL_HPP_
//************************************************************************
#include "point3d.hpp"
//************************************************************************
class Helicopter;
//************************************************************************
// Класс, представляющий журнал полета вертолета
class FlightJournal
{
/*-----------------------------------------------------------------*/
public:
/*-----------------------------------------------------------------*/
// Конструктор с аргументами:
// - ссылка на связанный объект вертолет
// - максимальное количество фиксируемых точек
FlightJournal (
const Helicopter & _helicopter
, int _nMaxLocations
);
// Удаленный конструктор копий и оператор присвоения
FlightJournal ( const FlightJournal & ) = delete;
FlightJournal & operator = ( const FlightJournal & ) = delete;
// Деструктор
~FlightJournal ();
// Метод доступа к связанному с журналом вертолету
const Helicopter & getHelicopter () const;
// Метод для извлечения максимального количества точек для фиксации
int getMaxPositionsCount () const;
// Метод для извлечения количества уже зафиксированных точек
int getPositionsCount () const;
// Метод для извлечения координат конкретной зафиксированной точки
Point3D getPosition ( int _index ) const;
// Метод фиксации текущего местоположения вертолета
void trackPosition ();
// Метод вычисления длины пути вертолета в полете
double totalDistance () const;
/*-----------------------------------------------------------------*/
private:
/*-----------------------------------------------------------------*/
// Ссылка на объект-вертолет
const Helicopter & m_helicopter;
// Количество и массив данных точек местоположения
const int m_nMaxPositions;
Point3D * m_pPositions;
// Количество уже заполненных точек местоположения
int m_nUsedPositions;
/*-----------------------------------------------------------------*/
};
//************************************************************************
// Метод доступа к связанному с журналом вертолету
inline const Helicopter &
FlightJournal::getHelicopter () const
{
return m_helicopter;
}
//************************************************************************
// Метод для извлечения максимального количества точек для фиксации
inline int
FlightJournal::getMaxPositionsCount () const
{
return m_nMaxPositions;
}
//************************************************************************
// Метод для извлечения количества уже зафиксированных точек
inline int
FlightJournal::getPositionsCount () const
{
return m_nUsedPositions;
}
//************************************************************************
#endif // _FLIGHTJOURNAL_HPP_
flightjournal.cpp
#include "flightjournal.hpp"
#include "helicopter.hpp"
#include <stdexcept>
//************************************************************************
// Конструктор с аргументами
FlightJournal::FlightJournal (
const Helicopter & _helicopter,
int _nMaxPositions
)
: m_helicopter( _helicopter )
, m_nMaxPositions( _nMaxPositions )
, m_nUsedPositions( 0 ) // <- Изначально нет данных о местоположении
{
// Инвариант: максимальное число позиций должно быть положительным
if ( m_nMaxPositions < 1 )
throw std::logic_error( "Number of positions must be positive" );
// Выделяем массив фиксированного размера для хранения координат позиций
m_pPositions = new Point3D[ m_nMaxPositions ];
}
//************************************************************************
// Деструктор
FlightJournal::~FlightJournal ()
{
// Освобождаем массив координат
delete[] m_pPositions;
}
//************************************************************************
// Метод для извлечения координат конкретной зафиксированной точки
Point3D FlightJournal::getPosition ( int _index ) const
{
// Проверяем корректность индекса точки
if ( _index >= 0 && _index < m_nUsedPositions )
// Возвращаем координаты этой точки
return m_pPositions[ _index ];
else
// Исключение: индекс позиции за допустимыми пределами
throw std::logic_error( "Position index is out of range" );
}
//************************************************************************
// Метод фиксации текущего местоположения вертолета
void FlightJournal::trackPosition ()
{
// Проверяем наличие свободного места в массиве для очередной точки
if ( m_nUsedPositions == m_nMaxPositions )
// Исключение: выделенная в конструкторе память исчерпана
throw std::logic_error( "No space left in the journal" );
// Извлекаем текущие координаты вертолета и запоминаем его местоположение
Point3D currentPosition = m_helicopter.getCurrentPosition();
m_pPositions[ m_nUsedPositions++ ] = currentPosition;
}
//************************************************************************
// Метод вычисления длины пути вертолета в полете
double FlightJournal::totalDistance () const
{
double result = 0.0;
for ( int i = 0; i < m_nUsedPositions - 1; i++ )
result += m_pPositions[ i ].distanceTo( m_pPositions[ i + 1 ] );
return result;
}
//************************************************************************
Структура объекта-журнала, инициализированного с максимальным количеством точек, равным 4, в памяти выглядит приблизительно следующим образом:
Основной проблемой данного решения является невозможность изменения количества фиксируемых местоположений вертолета. В этой задаче, как и в большинстве реальных задач, маловероятно, что конкретное число местоположений будет известно заранее. Это прекрасный случай применения ранее изученного STL-контейнера std::vector. Перепишем пример с журналом полета вертолета с применением вектора для хранения координат вертолета. Поскольку вектор полностью берет на себя задачу управления внутренним массивом данных с координатами в зафиксированных точках, реализация такого отношения существенно упрощается:
flightjournal_v2.hpp
#ifndef _FLIGHTHJOURNAL_V2_HPP_
#define _FLIGHTHJOURNAL_V2_HPP_
//************************************************************************
#include "point3d.hpp"
#include <vector>
//************************************************************************
class Helicopter;
//************************************************************************
// Класс, представляющий журнал полета вертолета
class FlightJournal
{
/*-----------------------------------------------------------------*/
public:
/*-----------------------------------------------------------------*/
// Конструктор с только одним аргументом: ссылка на связанный объект-вертолет
// Больше не нужно передавать максимальное количество позиций!
FlightJournal ( const Helicopter & _helicopter );
// Исчезает необходимость в запрещении конструктора копий и оператора присвоения
// Деструктор тоже не нужен!
// Метод доступа к связанному с журналом вертолету
const Helicopter & getHelicopter () const;
// Не нужен и метод, возвращающий максимальное количество позиций!
// Метод для извлечения количества уже зафиксированных точек
int getPositionsCount () const;
// Метод для извлечения координат конкретной зафиксированной точки
Point3D getPosition ( int _index ) const;
// Метод фиксации текущего местоположения вертолета
void trackPosition ();
// Метод вычисления длины пути вертолета в полете
double totalDistance () const;
/*-----------------------------------------------------------------*/
private:
/*-----------------------------------------------------------------*/
// Ссылка на объект-вертолет
const Helicopter & m_helicopter;
// Вектор зафиксированных точек
std::vector< Point3D > m_positions;
/*-----------------------------------------------------------------*/
};
//************************************************************************
// Метод доступа к связанному с журналом вертолету
inline const Helicopter &
FlightJournal::getHelicopter () const
{
return m_helicopter;
}
//************************************************************************
// Метод для извлечения количества уже зафиксированных точек
inline int
FlightJournal::getPositionsCount () const
{
return m_positions.size();
}
//************************************************************************
#endif // _FLIGHTHJOURNAL_V2_HPP_
flightjournal_v2.cpp
#include "flightjournal_v2.hpp"
#include "helicopter.hpp"
//************************************************************************
// Конструктор с аргументами
FlightJournal::FlightJournal ( const Helicopter & _helicopter )
: m_helicopter( _helicopter )
// Конструктор вектора вызовется автоматически
{
// Никаких инвариантов проверять не нужно
}
//************************************************************************
// Метод для извлечения координат конкретной зафиксированной точки
Point3D FlightJournal::getPosition ( int _index ) const
{
// Проверку индекса осуществляет сам вектор
return m_positions.at( _index );
}
//************************************************************************
void FlightJournal::trackPosition ()
{
// Больше нет ограничений на количество точек.
// Просто извлекаем текущие координаты вектора и кладем их в вектор
Point3D currentPosition = m_helicopter.getCurrentPosition();
m_positions.push_back( currentPosition );
}
//************************************************************************
// Метод вычисления длины пути вертолета в полете
double FlightJournal::totalDistance () const
{
double result = 0.0;
for ( int i = 0; i < m_positions.size() - 1; i++ )
result += m_positions[ i ].distanceTo( m_positions[ i + 1 ] );
return result;
}
//************************************************************************
Новая структура выглядит в памяти следующим образом:
Разумеется, внутри объекта-вектора в той или иной форме хранится адрес блока данных с объектами Point3D в динамической памяти, количество используемых и выделенных элементов (чаще вместо хранения количества как числа, реализация стандартной библиотеки хранит адреса элементов, следующих в блоке данных за последним заполненным элементом и последним выделенным).
Помимо того, что такая реализация предоставляет решение основной проблемы - динамическое изменение количества фиксируемых позиций вертолета - благодаря использованию вектора удается достичь существенных упрощений реализации:
нет необходимости в передаче и извлечении максимального числа точек;
исчезает необходимость в явном деструкторе, поскольку деструктор вектора, который освободит выделенный блок памяти, будет автоматически вызван из генерируемого компилятором автоматического деструктора класса FlightJournal;
исчезает необходимость в запрещении копирования, поскольку реализация класса std::vector обладает функциональностью корректного копирования своего содержимого, и никаких "висячих" указателей в автоматическом конструкторе копий не возникнет;
вектору также можно доверить проверку индексов при доступе к конкретным точкам.
