- •Ооп: Лекция 10. Более сложные формы композиции.
- •Типичные ошибки при реализации родительских классов
- •Рекомендованный стиль интерфейса родительских классов
- •Классы-родители с применением итераторов контейнеров
- •Умные указатели std::unique_ptr
- •Использование std::unique_ptr для композиции объектов
- •Итерирование дочерних элементов с учетом умных указателей
- •Использование средства std::function
- •Использование отображений при реализации композиции
- •Использование множеств
- •Композиция с кратностью многие-ко-многим
- •Полные примеры из лекции
Композиция с кратностью многие-ко-многим
Отношение между объектами Book и Author более сложное, чем кажется на первый взгляд. С одной стороны, авторами одной и той же книги может быть несколько человек. С другой стороны - один автор мог участвовать в написании нескольких разных книг. Отношение с такой кратностью называют композицией многие-ко-многим. Это наиболее сложный вид композиции из всех возможных.
Отношение многие-ко-многим в большинстве случаев должно быть реализовано без ответственности за уничтожение. Такое отношение образует сложный граф связей между объектами, и понять используется объект где-либо еще достаточно сложно. Часто отношение многие-ко-многим представляет в предметной области некоторое логическое объединение нескольких сущностей в группу, нежели более сильное отношение владения, предполагающее ответственность за уничтожение. Обычно объекты, связанные отношением многие-ко-многим, создает и уничтожает какой-то третий объект, ответственный за хранение всех участвующих в такой модели объектов. В примере о книгах и авторах такую роль мог бы играть класс Library (библиотека).
Важной особенностью реализации такого вида композиции является обязательность использования форвардных деклараций в заголовочных файлах. Допустим, это ограничение нарушено, и программист использует директивы #include, чтобы связать определения классов друг с другом:
book.hpp
#include “author.hpp”
class Book
{
// ...
};
author.hpp
#include “book.hpp”
class Author
{
// ...
};
Попытка сборки проекта с таким подходом неизбежно приведет к зависанию либо фатальному сбою компилятора. Обрабатывая цепочку директив #include, препроцессор не сможет выбраться из бесконечной рекурсии, поскольку файлы включают друг друга.
Использование форвардных деклараций позволяет разорвать этот замкнутый круг. Директивы #include, свою очередь, размещают не в заголовочных файлах, а в файлах реализации классов. При этом, заголовочные файлы не могут использовать полные определения классов своих дочерних объектов. Соответственно, объявления всех полей и методов, как максимум, могут использовать эти классы только в виде указателя или ссылки, не обращаясь к содержимому. Работа с содержимым, при этом, в свободном доступе в CPP-файлов классов, поскольку они компилируются отдельно друг от друга и не образуют бесконечную рекурсию при компиляции.
Ниже представлен набросок примера с композицией многие-ко-многим, а полную версию можно посмотреть по ссылке в конце лекции.
author.hpp
#ifndef _AUTHOR_HPP_
#define _AUTHOR_HPP_
/*****************************************************************************/
#include <unordered_set>
#include <memory>
#include <functional>
#include <string>
/*****************************************************************************/
// Форвардное объявление
class Book;
/*****************************************************************************/
class Author
{
/*-----------------------------------------------------------------*/
public:
/*-----------------------------------------------------------------*/
// …
// Метод, возвращающий количество книг автора
int getBooksCount () const;
// Метод, выясняющий участвовал ли автор в написании указанной книги
bool hasBook ( Book const & _book ) const;
// Метод, регистрирующий очередную книгу автора
void addBook ( Book const & _book );
// Метод, отменяющий регистрацию одной из книг автора
void removeBook ( Book const & _book );
// Метод очистки набора книг
void clearBooks ();
// Метод обхода набора книг с применением указанного пользовательского действия
void forEachBook ( std::function< void ( Book const & ) > _action ) const;
/*-----------------------------------------------------------------*/
private:
/*-----------------------------------------------------------------*/
// …
// Множество книг автора
std::unordered_set< Book const * > m_books;
/*-----------------------------------------------------------------*/
};
/*****************************************************************************/
#endif // _AUTHOR_HPP_
author.cpp
#include “author.hpp”
#include “book.hpp”
// Реализация методов автора ...
library.hpp
#ifndef _LIBRARY_HPP_
#define _LIBRARY_HPP_
/*****************************************************************************/
#include <vector>
#include <memory>
/*****************************************************************************/
// Форвардные объявления
class Book;
class Author;
/*****************************************************************************/
class Library
{
/*-----------------------------------------------------------------*/
public:
/*-----------------------------------------------------------------*/
void addBook ( std::unique_ptr< Book > _book );
// ... другие полезные методы для работы с книгами
/*-----------------------------------------------------------------*/
void addAuthor ( std::unique_ptr< Author > _authors );
// ... другие полезные методы для работы с авторами
/*-----------------------------------------------------------------*/
private:
/*-----------------------------------------------------------------*/
// Книги библиотеки
std::vector< std::unique_ptr< Book > > m_books;
// Авторы книг библиотеки
std::vector< std::unique_ptr< Author > > m_authors;
/*-----------------------------------------------------------------*/
};
/*****************************************************************************/
#endif // _LIBRARY_HPP_
library.сpp
#include “library.hpp”
#include “author.hpp”
#include “book.hpp”
// Реализация методов библиотеки…
