ООП: Лекция 9. Композиция объектов. 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

ООП: Лекция 9. Композиция объектов.



ООП: Лекция 9. Композиция объектов.

Версия 3.0 27 августа 2016г.

(С) 2013-2016, Зайченко Сергей Александрович, к.т.н, ХНУРЭ, доцент кафедры АПВТ

 

Композиция объектов

 

КОМПОЗИЦИЯ (или агрегирование, включение) - простейший способ создания новых более сложных классов путем объединения нескольких объектов существующих классов в единое целое.

 

Между классом верхнего и нижнего уровня обычно присутствует отношение "целое-часть". Ниже приведен простейший пример композиции — объект двигатель (Engine) является частью объекта автомобиль (Car):

 

engine.hpp

 

#ifndef _ENGINE_HPP_

#define _ENGINE_HPP_

 

//************************************************************************

 

// Класс для двигателя

class Engine

{

 

/*-----------------------------------------------------------------*/

 

public:

 

/*-----------------------------------------------------------------*/

 

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

Engine (float _horsePower)

: m_horsePower(_horsePower)

{}

 

// Метод доступа к мощности

float getHorsePower () const { return m_horsePower; }

 

/*-----------------------------------------------------------------*/

 

private:

 

/*-----------------------------------------------------------------*/

 

// Мощность в лошадиных силах

const float m_horsePower;

 

/*-----------------------------------------------------------------*/

 

};

 

#endif // _ENGINE_HPP_

 

 

car.hpp

 

#ifndef _CAR_HPP_

#define _CAR_HPP_

 

//************************************************************************

 

#include <string>

#include <iostream>

 

//************************************************************************

 

// Класс для автомобиля

class Car

{

 

/*-----------------------------------------------------------------*/

 

public:

 

/*-----------------------------------------------------------------*/

 

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

Car (const std::string & _model, float _engineHorsePower);

 

// Метод доступа к двигателю

const Engine & getEngine () const;

 

// Метод доступа к названию модели

const std::string & getModel () const

 

// Метод вывода описания машины в указанный поток

void describe (std::ostream & _o) const

 

/*-----------------------------------------------------------------*/

 

private:

 

/*-----------------------------------------------------------------*/

 

// Название модели

std::string m_model;

 

// Дочерний объект-двигатель

Engine m_engine;

 

/*-----------------------------------------------------------------*/

 

};

 

//************************************************************************

 

// Метод доступа к дочернему объекту-двигателю

inline const Engine &

Car::getEngine () const

{

return m_engine;

}

 

//************************************************************************

 

// Метод доступа к модели

inline const std::string &

Car::getModel () const

{

return m_model;

}

 

//************************************************************************

 

#endif // _CAR_HPP_

 

 

car.cpp

 

//************************************************************************

 

#include “car.hpp”

 

//************************************************************************

 

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

Car::Car (const std::string & _model, float _engineHorsePower)

: m_model(_model),

m_engine(_engineHorsePower) // <- вызов конструктора дочернего объекта

{}

 

//************************************************************************

 

// Тестовый метод, печатает информацию об автомобиле в поток

void Car::describe (std::ostream & _o) const

{

_o << "Model: " << getModel()

<< ", horse power: " << m_engine.getHorsePower()

<< std::endl; // ^- обращение к дочернему объекту

}

 

//************************************************************************

 

В памяти поля дочернего объекта размещаются внутри родительского объекта целиком:

 

К слову, объект std::string m_model также является дочерним объектом, и размещается полностью в родительском объекте Car, с той разницей, что модель - является объектом-значением, а двигатель - объектом-сущностью.

 

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

 

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

 

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

 

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

Car::Car (const std::string & _model, float _engineHorsePower)

: m_model(_model),

// ^ вызов конструктора копий дочернего объекта №1

m_engine(_engineHorsePower )

// ^ вызов обычного конструктора дочернего объекта №2

{

}

 

В свою очередь, дочерний объект-строка также инициализируется в списке инициализации, используя конструктор копий. Его присвоение возможно и в теле конструктора Car в отличие от двигателя, поскольку класс std::string имеет конструктор по умолчанию:

 

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

Car::Car (const std::string & _model, float _engineHorsePower)

: m_engine(_engineHorsePower )

// ^ вызов обычного конструктора дочернего объекта №2

{

m_model = _model;

}

 

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

 

Вызов деструкторов дочерних объектов, хранящихся в родительском объекте по значению, размещается автоматически в деструкторе родительского класса. Это справедливо как для случая автоматически генерируемого деструктора родительского класса, так и для случая, когда имеется собственный деструктор. Явно деструктор дочерних объектов вызывать не требуется.

 

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

 

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

 

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

 

Inline double

Circle::getRadius () const

{

return m_radius;

}

 

//************************************************************************

 

#endif // _CIRCLE_HPP_

 

 

circle.cpp

 

#include "circle.hpp"

 

#define _USE_MATH_DEFINES // <- необходимо для работы с константой M_PI

#include <cmath>

 

#include <stdexcept>

 

//************************************************************************

 

// Реализация конструктора

Circle::Circle (Point3D _center, double _radius)

: m_center(_center) // <= вызов конструктора копий Point3D

, m_radius(_radius)

{

// Инвариант: радиус должен быть положительным

if (m_radius <= 0.0)

throw std::logic_error("Circle radius must be positive");

}

 

//************************************************************************

 

// Реализация метода вычисления длины окружности

double Circle::getPerimeter () const

{

return 2.0 * M_PI * m_radius;

}

 

//************************************************************************

 

// Реализация метода вычисления площади окружности

double Circle::getArea () const

{

return M_PI * m_radius * m_radius;

}

 

//************************************************************************

 

// Реализация метода вычисления координат точки на окружности под заданным углом

Point3D Circle::getPointOn (double _angle) const

{

// Вычисляем длины катетов по гипотенузе (радиусу) и углу

double xOffset = m_radius * cos(_angle);

double yOffset = m_radius * sin(_angle);

 

// Создаем новый объект-точку

return Point3D(m_center.getX() + xOffset,

m_center.getY() + yOffset,

m_center.getZ()

);

}

 

//************************************************************************

 

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

 

 

cylinder.hpp

 

#ifndef _CYLINDER_HPP_

#define _CYLINDER_HPP_

 

//************************************************************************

 

#include " circle.hpp"

 

//************************************************************************

 

class Cylinder

{

/*-----------------------------------------------------------------*/

 

public:

 

/*-----------------------------------------------------------------*/

 

// Конструктор. Передаем основание по ссылке.

// Передача основания по значению также допустима.

Cylinder (Circle _base, double _height);

 

// Метод доступа к объекту-основанию

Circle getBase () const;

 

// Метод доступа к высоте цилиндра

double getHeight () const;

 

// Метод вычисления объема цилиндра

double getVolume () const;

 

// Метод вычисления площади поверхности сторон

double getSideSurfaceArea () const;

 

// Метод вычисления всей площади поверхности

double getFullSurfaceArea () const;

 

// Метод вычисления координат точки на поверхности под определенным углом

// и на заданной высоте

Point3D getPointOnSurface (double _angle, double _height) const;

 

/*-----------------------------------------------------------------*/

 

private:

 

/*-----------------------------------------------------------------*/

 

// Основание цилиндра — дочерний объект класса Circle

Circle m_base;

 

// Высота цилиндра

double m_height;

 

/*-----------------------------------------------------------------*/

 

};

 

//************************************************************************

 

// Реализация метода доступа к объекту-основания

inline Circle

Cylinder::getBase () const

{

return m_base;

}

 

//************************************************************************

 

// Реализация метода доступа к высоте

Inline double

Cylinder::getHeight () const

{

return m_height;

}

 

//************************************************************************

 

#endif // _CYLINDER_HPP_

 

 

cylinder.cpp

 

//************************************************************************

 

#include "cylinder.hpp"

#include <cmath>

#include <stdexcept>

 

//************************************************************************

 

// Реализация конструктора

Cylinder::Cylinder (const Circle & _base, double _height)

: m_base(_base) // <= вызов конструктора копий класса Circle

, m_height(_height)

{

// Инвариант: высота должна быть положительной

if (m_height <= 0.0)

throw std::logic_error("Cylinder height must be positive");

}

 

//************************************************************************

 

// Реализация метода вычисления объема

double Cylinder::getVolume () const

{

return m_base.getArea() * m_height;

}

 

//************************************************************************

 

// Реализация метода вычисления площади поверхности сторон

double Cylinder::getSideSurfaceArea () const

{

return m_base.getPerimeter() * m_height;

}

 

//************************************************************************

 

// Реализация метода вычисления площади полной поверхности

double Cylinder::getFullSurfaceArea () const

{

return getSideSurfaceArea() + 2.0 * m_base.getArea();

}

 

//************************************************************************

 

// Реализация метода вычисления координат точки на поверхности

Point3D Cylinder::getPointOnSurface (double _angle, double _height) const

{

// Вычисляем координаты точки на основании

Point3D circlePoint = m_base.getPointOn(_angle);

 

// Создаем новую точку с учетом высоты

return Point3D(circlePoint.getX(),
circlePoint.getY(),
circlePoint.getZ() + _height);

}

 

//************************************************************************

 

Else

throw std::logic_error("Point index is out of range");

}

 

//************************************************************************

 

// Реализация метода доступа к точке по индексу c правом на запись

Point3D & Polygon::getPoint (int _index)

{

if (_index >= 0 && _index < getNPoints())

return m_points[ _index ];

Else

throw std::logic_error("Point index is out of range");

}

 

//************************************************************************

 

// Реализация метода вычисления длины сторон многоугольника

double Polygon::getPerimeter () const

{

// Суммируем расстояния между соседними точками многоугольника

double total = 0.0;

for (int i = 0; i < m_nPoints - 1; i++)

total += m_points[ i ].distanceTo(m_points[ i + 1 ]);

return total;

}

 

//************************************************************************

 

Inline int

Weapon::getCurrentAmmo () const

{

return m_currentAmmo;

}

 

//************************************************************************

 

#endif //_WEAPON_HPP_

 

 

weapon.cpp

 

#include "weapon.hpp"

#include "weapontype.hpp"

 

//************************************************************************

 

// Реализация конструктора

Weapon::Weapon (const WeaponType & _type, int _initialAmmo)

: m_type(_type) // запоминаем ссылку на дочерний объект

, m_currentAmmo(_initialAmmo)

{

}

 

 

//************************************************************************

 

// Реализация метода определения полноты заряда

bool Weapon::hasFullAmmo () const

{

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

// максимально возможным для данного типа орудия

return m_currentAmmo == getType().getMaxAmmo();

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

}

 

//************************************************************************

 

// Реализация метода определения отсутствия зарядов

bool Weapon::hasNoAmmo () const

{

return m_currentAmmo == 0;

}

 

//************************************************************************

 

// Реализация метода загрузки зарядов

int Weapon::loadAmmo (int _ammo)

{

// Определяем количество не хватающих до полной загрузки зарядов

int maxAmmo = getType().getMaxAmmo();

int freeAmmoSpace = maxAmmo - m_currentAmmo;

 

// Достаточно ли имеется зарядов до полной загрузки?

if (_ammo >= freeAmmoSpace)

{

// Возвращаем число неиспользованных зарядов

m_currentAmmo = maxAmmo;

return _ammo - freeAmmoSpace;

}

Else

{

// Переданного количества зарядов не хватит для полной загрузки

m_currentAmmo += _ammo;

return 0;

}

}

 

//************************************************************************

 

// Реализация метода имитации выстрела

bool Weapon::tryShoot ()

{

// Имеется ли хоть один заряд?

if (hasNoAmmo())

// Без зарядов стрелять трудно:-(

return false;

 

// Выстрел произведен. Уменьшаем число оставшихся зарядов на 1.

-- m_currentAmmo;

return true;

}

 

//************************************************************************

 

test_weapons.cpp

 

#include "weapon.hpp"

#include "weapontype.hpp"

 

#include <cassert>

 

int main ()

{

// Создаем тип орудия — калибр 125мм, максимум 5 зарядов

WeaponType * pType = new WeaponType(125.0, 5);

 

// Создаем два орудия данного типа без начального заряда

Weapon * pWeapon1 = new Weapon(* pType);

Weapon * pWeapon2 = new Weapon(* pType);

 

// Заряжаем первое орудие максимально, убеждаемся, что орудие забрало

// зарядов, соответствующее максимально возможному для типа орудия

int totalAmmo = 100;

totalAmmo = pWeapon1->loadAmmo(totalAmmo);

assert(totalAmmo == (100 - pType->getMaxAmmo()));

 

// Заряжаем второе орудие только 1 снарядом

pWeapon2->loadAmmo(1);

 

// Делаем выстрел из первого орудия, убеждаемся, что число зарядом уменьшилось на 1

assert(pWeapon1->getCurrentAmmo() == pType->getMaxAmmo());

bool success = pWeapon1->tryShoot();

assert(success && pWeapon1->getCurrentAmmo() == (pType->getMaxAmmo() - 1));

 

// Делаем выстрел из второго орудия

assert(pWeapon2->getCurrentAmmo() == 1);

success = pWeapon2->tryShoot();

// Убеждаемся, что выстрел произведен, и что зарядов в орудии не осталось

assert(success && pWeapon2->hasNoAmmo());

 

// Второй выстрел из второго орудия не получится

success = pWeapon2->tryShoot();

assert(! success);

 

// Уничтожаем орудия

delete pWeapon1;

delete pWeapon2;

 

// Уничтожаем тип орудия

delete pType;

}

 

Inline int

Helicopter::getMachineID () const

{

return m_machineID;

}

 

 

//************************************************************************

 

// Реализация метода доступа к текущей позиции

inline Point3D

Helicopter::getCurrentPosition () const

{

return m_position;

}

 

//************************************************************************

 

// Реализация метода изменения текущей позиции

Inline void

Helicopter::moveTo (Point3D _p)

{

m_position = _p;

}

 

//************************************************************************

 

// Реализация метода доступа к текущему углу

inline Vector3D

Helicopter::getCurrentAngle () const

{

return m_currentAngle;

}

 

//************************************************************************

 

// Реализация метода изменения текущего угла

Inline void

Helicopter::turnTo (Vector3D _angle)

{

m_currentAngle = _angle;

}

 

//************************************************************************

 

// Реализация метода подтверждения наличия орудия

Inline

bool Helicopter::hasWeapon () const

{

// Если орудие установлено, значение указателя будет отлично от nullptr

return m_pWeapon!= nullptr;

}

 

//************************************************************************

 

// Реализация метода доступа к установленному орудию

Inline

Weapon * Helicopter::getWeapon () const

{

// Даже если орудия не установлено, все корректно — метод вернет nullptr

return m_pWeapon;

}

 

//************************************************************************

 

#endif //_HELICOPTER_HPP_

 

 

helicopter.cpp

 

#include "helicopter.hpp"

#include "weapon.hpp" // Включаем полное определение класса-орудия лишь сейчас,

// поскольку для реализации методов класса-вертолета используется

// содержимое класса-орудия (для выстрела, для уничтожения)

 

#include <stdexcept>

 

//************************************************************************

 

// Реализация конструктора

Helicopter::Helicopter (

int _machineID

, Vector3D _initialAngle

, Point3D _initialPosition

)

// Копируем номер, угол и координаты

: m_machineID(_machineID)

, m_currentAngle(_initialAngle)

, m_position(_initialPosition)

, m_pWeapon(nullptr) // <- Изначально орудие не устанавливается

{

}

 

//************************************************************************

 

// Реализация деструктора

Helicopter::~Helicopter ()

{

// Уничтожаем орудие, если оно установлено.

// Если орудия не установлено, ничего страшного, delete nullptr игнорируется

delete m_pWeapon;

}

 

//************************************************************************

 

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

void Helicopter::installWeapon (Weapon & _weapon)

{

// Убеждаемся, что никакое другое орудие еще не было установлено

if (hasWeapon())

throw std::logic_error("Weapon is already installed on the helicopter");

 

// Создаем связь между объектами:
// запоминаем в объекте-вертолет адрес объекта-орудия

m_pWeapon = & _weapon;

}

 

//************************************************************************

 

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

void Helicopter::deinstallWeapon ()

{

// Убеждаемся, что орудие было установлено

if (! hasWeapon())

throw std::logic_error("Weapon was not installed on the helicopter");

 

// Разрываем связь между объектами через обнуление указателя

m_pWeapon = nullptr;

 

// Примечание: при таком разрыве связи внешний код должен гарантировать

// освобождение объекта-орудия, поскольку вертолет больше за него не отвечает

}

 

//************************************************************************

 

// Реализация метода, осуществляющего пробу выстрела по заданной целевой координате

bool Helicopter::tryShootingTargetAt(Point3D _p)

{

// Если орудия не установлено, выстрелить не получится

if (! hasWeapon())

return false;

 

// Изменяем угол носа вертолета таким образом, чтобы вертолет смотрел на цель

turnTo(

Vector3D(

_p.getX() - m_position.getX(),

_p.getY() - m_position.getY(),

_p.getZ() - m_position.getZ()

)

);

 

// Пробуем выстрелить из орудия.

// Логика учета количества зарядов уже учтена в реализации класса-орудия

return m_pWeapon->tryShoot();

}

 

//************************************************************************

 

test_helicopter.cpp

 

#include "helicopter.hpp"

#include "weapontype.hpp"

#include "weapon.hpp"

 

#include <cassert>

 

int main ()

{

// Создаем два объекта-вертолета

Helicopter * pHelicopter1 = new Helicopter(1);

Helicopter * pHelicopter2 = new Helicopter(2);

 

// Создаем тип орудия

WeaponType * pWeaponType = new WeaponType(125.0, 5);

 

// Вооружаем первый вертолет орудием с одним начальным зарядом

pHelicopter1->installWeapon(* new Weapon(* pWeaponType, 1));

assert(pHelicopter1->getWeapon()->getCurrentAmmo() == 1);

 

// Пробуем выстрелить c первого вертолета.

// Первый выстрел успешен, а орудие разряжено.

bool result = pHelicopter1->tryShootingTargetAt(Point3D(2.0, 2.0, 2.0));

assert(result && pHelicopter1->getWeapon()->hasNoAmmo());

 

// Пробуем выстрелить еще раз с первого вертолета.

// Второй выстрел завершается неудачей, поскольку нет зарядов в орудии

result = pHelicopter1->tryShootingTargetAt(Point3D(3.0, 3.0, 3.0));

assert(! result);

 

// Пробуем выстрелить со второго вертолета, однако терпим неудачу,

// поскольку никакого орудия на нем не установлено

result = pHelicopter2->tryShootingTargetAt(Point3D(4.0, 4.0, 4.0));

assert(! result);

 

// Куда же направлены носы наших вертолетов?

// - первый вертолет имеет вооружение, и поворачивается перед попыткой выстрела,

// соответственно, его нос направлен в сторону координаты последней цели

// - второй вертолет не имеет установленного орудия, и не поворачивается

assert(pHelicopter1->getCurrentAngle() == Vector3D(3.0, 3.0, 3.0));

assert(pHelicopter2->getCurrentAngle() == Vector3D(0.0, 0.0, 0.0));

 

// Уничтожаем оба вертолета. Орудие уничтожается вместе с первым вертолетом

delete pHelicopter1;

delete pHelicopter2;

 

// Уничтожаем тип орудия

delete pWeaponType;

}

 

Helicopterpad.hpp

 

#ifndef _HELICOPTERPAD_HPP_

#define _HELICOPTERPAD_HPP_

 

//************************************************************************

 

#include "point3d.hpp"

 

//************************************************************************

 

// Форвардное объявление класса-вертолета.

// Его содержимое не требуется для объявления класса-площадки.

class Helicopter;

 

//************************************************************************

 

// Класс-площадка

class HelicopterPad

{

 

/*-----------------------------------------------------------------*/

 

public:

 

/*-----------------------------------------------------------------*/

 

// Конструктор — задает координату местоположения, но не задает вертолета

HelicopterPad (Point3D _location);

 

// Запрещенные конструктор копий и оператор присвоения

HelicopterPad (const HelicopterPad &) = delete;

HelicopterPad & operator = (const HelicopterPad &) = delete;

 

// Метод доступа к местоположению площадки

Point3D getLocation () const;

 

// Метод подтверждения приземления вертолета

bool hasLanded () const;

 

// Метод доступа к возможно приземлившемуся вертолету

Helicopter * getLanded () const;

 

// Метод регистрации приземления вертолета

void land (Helicopter & _helicopter);

 

// Метод регистрации взлета вертолета

void unland ();

 

/*-----------------------------------------------------------------*/

 

private:

 

/*-----------------------------------------------------------------*/

 

// Координаты местоположения площадки.

// В отличие от вертолета, координаты площадки не могут изменяться

const Point3D m_location;

 

// Необязательная связь с приземлившимся объектом-вертолетом

Helicopter * m_pLanded;

 

/*-----------------------------------------------------------------*/

 

};

 

//************************************************************************

 

// Реализация метода доступа к местоположению площадки

inline Point3D

HelicopterPad::getLocation () const

{

return m_location;

}

 

//************************************************************************

 

// Реализация метода подтверждения приземления вертолета

inline bool HelicopterPad::hasLanded () const

{

// Если вертолет приземлился, значение указателя будет отлично от nullptr

return m_pLanded!= nullptr;

}

 

//************************************************************************

 

// Реализация метода доступа к возможно приземлившемуся вертолету

inline Helicopter *

HelicopterPad::getLanded () const

{

// Даже если вертолет не приземлился, все корректно — метод вернет nullptr

return m_pLanded;

}

 

//************************************************************************

 

#endif //_HELICOPTERPAD_HPP_

 

 

helicopterpad.cpp

 

#include "helicopterpad.hpp"

#include "helicopter.hpp"

#include "point3d.hpp"

 

#include <stdexcept>

 

//************************************************************************

 

// Реализация конструктора

HelicopterPad::HelicopterPad (Point3D _location)

: m_location(_location),

m_pLanded(nullptr) // Изначально вертолет не приземлялся

{

}

 

//************************************************************************

 

// Реализация метода регистрации приземления вертолета

void HelicopterPad::land (Helicopter & _helicopter)

{

// Убеждаемся, что другой вертолет не приземлялся

if (m_pLanded)

throw std::logic_error("Something has already landed on this pad");

 

// Подводим вертолет к точке приземления

_helicopter.moveTo(getLocation());

 

// Создаем связь между объектами —
// запоминаем адрес объекта-вертолета в объекте-площадке

m_pLanded = & _helicopter;

}

 

//************************************************************************

 

// Реализация метода регистрации взлета вертолета

void HelicopterPad::unland ()

{

// Убеждаемся, что вертолет приземлялся

if (! m_pLanded)

throw std::logic_error("No helicopter is currently landed");

 

// Вертолет должен взлететь на 7м строго над площадкой

m_pLanded->moveTo(

Point3D(

getLocation().getX(),

getLocation().getY(),

getLocation().getZ() + 7.0

)

);

 

// Разрываем связь между объектами обнулением указателя

m_pLanded = nullptr;

}

 

//************************************************************************

 

test_pads.cpp

 

#include "helicopterpad.hpp"

#include "helicopter.hpp"

 

#include <cassert>

 

int main ()

{

// Создаем вертолетную площадку

HelicopterPad * pHeliPad = new HelicopterPad(Point3D(1.0, 2.0, 0.0));

 

// Изначально на площадке никто не приземлен

assert(! pHeliPad->hasLanded());

 

// Создаем вертолет

Helicopter * pHelicopter = new Helicopter(1);

 

// Приземляем вертолет на площадке

pHeliPad->land(* pHelicopter);

 

// Убеждаемся, что на площадке находится наш вертолет

assert(pHeliPad->hasLanded() && pHeliPad->getLanded() == pHelicopter);

 

// Убеждаемся, что текущее местоположение вертолета совпадает с местом площадки

Point3D location = pHelicopter->getCurrentPosition();

assert(location == pHeliPad->getLocation());

 

// Взлетаем

pHeliPad->unland();

 

// На площадке не должно остаться вертолета

assert(! pHeliPad->hasLanded());

 

// Координаты X/Y вертолета должны совпадать с координатами площадки,

// однако координата Z должна отличаться (вертолет взлетает идеально вверх)

Point3D newLocation = pHelicopter->getCurrentPosition();

assert(newLocation.getX() == location.getX() &&

newLocation.getY() == location.getY() &&

newLocation.getZ() > location.getZ());

 

// Уничтожаем площадку и вертолет

delete pHeliPad;

delete pHelicopter;

}

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();

}

 

//************************************************************************

 



Поделиться:


Последнее изменение этой страницы: 2017-02-17; просмотров: 149; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.217.182.45 (0.839 с.)