- •Ооп: Лекция 9. Композиция объектов.
- •Композиция объектов
- •Простейшая композиция по значению
- •Множественная композиция по значению
- •Ссылочная композиция — логическое объединение
- •Композиция с разрываемой связью
- •Разрываемая ссылочная композиция
- •Недетерминированная множественность композиции
- •Множественная композиция объектов-сущностей
- •Полные примеры из лекции
Композиция с разрываемой связью
Некоторые виды отношений целое-часть не являются постоянными. Дочерний объект может прикрепляться к родительскому в течение его жизни, а при необходимости — отцепляться и прикрепляться к другому родительскому объекту. Такое поведение свойственно отношениям, в которых дочерний объект имеет существенную долю обязанностей по сравнению с родительским объектом, и имеется практический смысл в его существовании вне родительского либо с другим родителем. При этом родительский объект по-прежнему может отвечать за уничтожение дочернего.
Ниже представлен класс, моделирующий вертолет (Helicopter). Вертолет имеет уникальный числовой номер. Вертолет находится в конкретный момент времени по конкретным координатам и его нос направлен под конкретным углом относительно осей координат. На вертолет может быть установлено необязательное к наличию орудие (объект Weapon). При наличии орудия, вертолет может производить выстрелы, в противном случае — только летать.
С точки зрения хранения данных в памяти, единственным способом для реализации такой необязательной связи между объектами является хранение указателя на дочерний объект в памяти родительского. Указатель будет адресовать конкретный дочерний объект, если связь установлена. При отсутствии связи значение указателя будет равно nullptr.
Связь между объектами Helicopter и Weapon не является обязательной. Она может устанавливаться внешне через вызов метода installWeapon, а затем разрываться через вызов deinstallWeapon. Во избежание ошибок объект-вертолет должен проверять, что может быть установлено только одно орудие, а также гарантировать, что снять орудие можно только после его установки. Если орудие прикреплено к вертолету на момент его уничтожения, орудие уничтожается вместе с ним.
helicopter.hpp
#ifndef _HELICOPTER_HPP_
#define _HELICOPTER_HPP_
//************************************************************************
#include "point3d.hpp"
#include "vector3d.hpp"
//************************************************************************
// Форвардное объявление класса-орудия. При определении класса-вертолета, // содержимое класса-орудия не используется. Лишь используется указатель на объект // этого класса, что не требует полного определения.
//
// Не делаем #include "weapon.h" до момента реальной необходимости
//
// Однако мы включили определение класса-точки и класса-вектора, // поскольку они используются по значению внутри класса-вертолет, // а значит компилятору нужно знать их содержимое.
class Weapon;
//************************************************************************
class Helicopter
{
/*-----------------------------------------------------------------*/
public:
/*-----------------------------------------------------------------*/
// Конструктор — передаем номер, начальный угол и позицию, орудие не предусмотрено
Helicopter (
int _machineID
, Vector3D _initialAngle = Vector3D()
, Point3D _initialPosition = Point3D()
);
// Запрещенные конструктор копий и оператор присвоения
Helicopter ( const Helicopter & ) = delete;
Helicopter & operator = ( const Helicopter & ) = delete;
// Деструктор
~Helicopter ();
// Метод, возвращающий номер вертолета
int getMachineID () const;
// Метод подтверждения наличия орудия
bool hasWeapon () const;
// Метод доступа к установленному орудию
Weapon * getWeapon () const;
// Метод установки орудия
void installWeapon ( Weapon & _weapon );
// Метод снятия орудия
void deinstallWeapon ();
// Метод, осуществляющий пробу выстрела по заданной целевой координате
bool tryShootingTargetAt ( Point3D _p );
// Методы доступа и изменения текущей позиции
Point3D getCurrentPosition () const;
void moveTo ( Point3D _p );
// Методы доступа и изменения текущего угла по отношению к координатным осям
Vector3D getCurrentAngle () const;
void turnTo ( Vector3D _angle );
/*-----------------------------------------------------------------*/
private:
/*-----------------------------------------------------------------*/
// Дочерний объект-орудие – связь не является обязательной, может быть nullptr
Weapon * m_pWeapon;
// Угол направления носа вертолета
Vector3D m_currentAngle;
// Текущие координаты местоположения вертолета
Point3D m_position;
// Уникальный числовой номер вертолета
const int m_machineID;
/*-----------------------------------------------------------------*/
};
//************************************************************************
// Реализация метода, возвращающего номер вертолета
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;
}
