Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ООП_ Лекция №09 - Композиция объектов.docx
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
754.21 Кб
Скачать

Композиция с разрываемой связью

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

Ниже представлен класс, моделирующий вертолет (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;

}