Рекомендованный стиль интерфейса родительских классов 


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



ЗНАЕТЕ ЛИ ВЫ?

Рекомендованный стиль интерфейса родительских классов



 

В целом, отношение композиции с недетерминированной кратностью один-ко-многим, подобное рассмотренному примеру Book-Chapter, встречается на практике достаточно часто. Имеет смысл предложить некие типовые рекомендации касательно публичных операций, необходимых родительским классам для организации эффективной работы с набором дочерних объектов, с сохранением достаточной гибкости. Такие операции можно формировать в разных ситуациях по аналогии, изменяя лишь фрагменты названий методов и типы аргументов.

 

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

 

- метод, возвращающий текущее количество дочерних объектов:

 

int get XXX Count() const;

 

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


XXX get XXX (int index) const;

 

- метод, добавляющий дочерний объект к родительскому:

 

void add XXX (XXX obj);

 

Разумеется, вместо XXX нужно подставить название или тип хранимых данных (например, Chapter).

 

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

 

void insert XXX At (XXX obj, int index);

 

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

 

int find XXX Index (XXX obj) const;

bool hasXXX (XXX obj) const;

 

Если реализация основана на векторе, подобные операции поиска нужно закладывать лишь для случаев с небольшим ожидаемым количеством элементов (до 10), т.к. такой примитивный алгоритм поиска на основе перебора и сравнения будет обладать линейной вычислительной сложностью.

 

В некоторых задачах может понадобиться функциональность удаления элементов. Удаление элементов может принимать несколько форм:

 

● удаление элемента, порядковый номер которого известен:

 

void remove XXX At (int index);

 

● удаление элемента, порядковый номер которого неизвестен (с поиском):

 

void remove XXX (XXX obj);

 

● удаление всех элементов:

 

void clearXXX ();

 

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

 

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

 

Свойства дочерних объектов Формы основных методов
Примитивный тип или небольшой класс-значение, не содержащий динамически выделяемых ресурсов - передаем и возвращаем по значению, свободно копируем classParent { //... private: std::vector< XXX > m_VecXXX; };   voidParent::addXXX (XXX x) { m_VecXXX.push_back(x); }   XXX Parent::getXXX (intindex) const { returnm_VecXXX.at(index); }   intParent::findXXXIndex (XXX obj) const { intnXXX = getXXXCount(); for(inti = 0; i < nXXX; i++) if(m_VecXXX[ i ] == obj) returni; return-1; }   intmain () { Parent p; p.add(XXX()); XXX obj = p.getXXX(0); intpos = p.findXXXIndex(obj); }
Класс-значение, содержащий динамически выделяемые ресурсы, либо крупный по размеру, но перемещение объекта по тем или иным причинам не используется   - передаем и возвращаем по константной ссылке, копируем только при регистрации, где это неизбежно classParent { //... private: std::vector< XXX > m_VecXXX; };   voidParent::addXXX (XXX const & x) { m_VecXXX.push_back(x); }   XXX const& Parent::getXXX (intindex) const { returnm_VecXXX.at(index); }   intParent::findXXXIndex (XXX const& obj) const { intnXXX = getXXXCount(); for(inti = 0; i < nXXX; i++) if(m_VecXXX[ i ] == obj) returni; return-1; }   intmain () { Parent p; p.add(XXX()); XXX const& obj = p.getXXX(0); intpos = p.findXXXIndex(obj); }
Класс-значение, содержащий динамически выделяемые ресурсы, либо крупный по размеру, который умеет эффективно перемещаться   - передаем по r-value ссылке при добавлении и перемещаем объект, в остальных случаях используем константные ссылки - либо передаем по значению и перемещаем объект, но только при передаче временных объектов   classParent { //... private: std::vector< XXX > m_VecXXX; };   voidParent::addXXX (XXX && x) { m_VecXXX.push_back(x); // перемещает }   intmain () { Parent p; p.add(XXX()); // временный   XXX x; // перемещаем постоянный p.add(std::move(x)); }   либо   voidParent::addXXX (XXX x) // передавать только // временные! { m_VecXXX.push_back(std::move(x)); }   intmain () { Parent p; p.add(XXX()); // временный   XXX x; // плохо, так копируется вызовом add p.add(x); }
Класс-сущность, с ответственностью за уничтожение   - храним и передаем указатели, при возврате используем константные либо обычные ссылки для удобства classParent { //... private: std::vector< XXX * > m_VecXXX; };   voidParent::addXXX (XXX * pXXX) { m_VecXXX.push_back(pXXX); }   XXX const& Parent::getXXX (intindex) const { return *(m_VecXXX.at(index)); }   intParent::findXXXIndex (XXX const& obj) const { intnXXX = getXXXCount(); for(inti = 0; i < nXXX; i++) if(m_VecXXX[ i ] == & obj) returni; return-1; }   intmain () { Parent p; p.add(new XXX()); XXX const& obj = p.getXXX(0); intpos = p.findXXXIndex(obj); }
Класс-сущность, без ответственности за уничтожение   - передаем и возвращаем по константной ссылке, но храним по константному указателю classParent { //... private: std::vector< XXX const * > m_VecXXX; };   voidParent::addXXX (XXX const& obj) { m_VecXXX.push_back(& obj); }   XXX const& Parent::getXXX (intindex) const { return *(m_VecXXX.at(index)); }   intParent::findXXXIndex (XXX const& obj) const { intnXXX = getXXXCount(); for(inti = 0; i < nXXX; i++) if(m_VecXXX[ i ] == & obj) returni; return-1; }   intmain () { Parent p; XXX x; p.add(x); XXX const& obj = p.getXXX(0); intpos = p.findXXXIndex(x); }

 

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

 

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

 

book.hpp

 

#ifndef _BOOK_HPP_

#define _BOOK_HPP_

 

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

 

#include <string>

#include <vector>

#include <initializer_list>

 

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

 

class Chapter;

 

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

 

class Book

{

 

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

 

public:

 

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

 

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

Book (std::string const & _title);

 

// Конструктор со списком глав

Book (std::string const & _title, std::initializer_list< Chapter * > _chapters)

 

// Удаленные конструктор копий и оператор присвоения

Book (const Book &) = delete;

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

 

// Деструктор

~Book ();

 

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

std::string const & getTitle () const;

 

// Метод, возвращающий количество глав

int getChaptersCount () const;

 

// Метод, возвращающий главу по порядковому номеру

Chapter & getChapter (int _index) const;

 

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

int findChapterIndex (Chapter const & _chapter) const;

 

// Метод, подтверждающий наличие главы в книге

bool hasChapter (Chapter const & _chapter) const;

 

// Метод, добавляющий главу в конец книги

void addChapter (Chapter * _pChapter);

 

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

void insertChapter (int _atIndex, Chapter * _pChapter);

 

// Метод, удаляющий главу из книги в конкретной позиции

void removeChapter (int _atIndex);

 

// Метод, удаляющий указанную главу из книги

void removeChapter (Chapter const & _chapter);

 

// Метод удаления всех глав

void clearChapters ();

 

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

 

private:

 

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

 

// Набор глав

std::vector< Chapter * > m_chapters;

 

// Название книги

const std::string m_title;

 

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

 

};

 

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


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

inline std::string const &

Book::getTitle () const

{

return m_title;

}

 

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

 

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

Inline int

Book::getChaptersCount () const

{

return m_chapters.size();

}

 

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

 

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

inline Chapter &

Book::getChapter (int _index) const

{

return * m_chapters.at(_index);

}

 

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

 

#endif // _BOOK_HPP_

 

 

book.cpp

 

#include "book.hpp"

#include "chapter.hpp"

 

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

 

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

Book::Book (std::string const & _title)

: m_title(_title)

{}

 

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

 

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

Book::Book (

std::string const & _title,

std::initializer_list< Chapter * > _chapters

)

: m_title(_title), m_chapters(_chapters)

{}

 

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

 

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

Book::~Book ()

{

// Очистка уничтожит каждую главу

clearChapters();

}

 

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

 

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

int Book::findChapterIndex (Chapter const & _chapter) const

{

int nChapters = getChaptersCount();

for (int i = 0; i < nChapters; i++)

if (m_chapters[ i ] == & _chapter)

return i; // глава найдена

 

return -1; // глава не найдена

}

 

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

 

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

bool Book::hasChapter (Chapter const & _chapter) const

{

// Если глава есть в книге, порядковый номер будет найден успешно

return findChapterIndex(_chapter)!= -1;

}

 

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

 

// Реализация метода добавления главы в конец книги

void Book::addChapter (Chapter * _pChapter)

{

m_chapters.push_back(_pChapter);

}

 

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

 

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

void Book::insertChapter (int _atIndex, Chapter * _pChapter)

{

m_chapters.insert(m_chapters.begin() + _atIndex, _pChapter);

}

 

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

 

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

void Book::removeChapter (int _atIndex)

{

delete m_chapters.at(_atIndex);

m_chapters.erase(m_chapters.begin() + _atIndex);

}

 

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

 

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

void Book::removeChapter (Chapter const & _chapter)

{

int index = findChapterIndex(_chapter);

if (index == -1)

throw std::logic_error("Chapter does not exists in book");

 

removeChapter(index);

}

 

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

 

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

void Book::clearChapters ()

{

// Уничтожаем каждую главу

for (Chapter * pChapter: m_chapters)

delete pChapter;

 

// Очищаем контейнер

m_chapters.clear();

}

 

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

 



Поделиться:


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

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