Являются ли (казалось бы) тенистые вещи приемлемыми по практическим соображениям?
Сначала немного информации о моем коде. Я пишу графический модуль моей 2D-игры. Мой модуль содержит более двух классов, но здесь я упомяну только два: Шрифт и GraphicsRenderer .
Шрифт предоставляет интерфейс для загрузки (и выпуска) файлов и ничего более. В моем заголовке Font я не хочу, чтобы какие-либо детали реализации просочились, и это включает типы данных сторонней библиотеки, которую я использую. Способ предотвращения отображения сторонней библиотеки в заголовке - неполный тип (я так понимаю, это стандартная практика):
class Font
{
private:
struct FontData;
boost::shared_ptr<FontData> data_;
};
GraphicsRenderer - это устройство (читай: singleton), которое инициализирует и финализирует стороннюю графическую библиотеку, а также используется для визуализации графических объектов (таких как как Шрифты , Изображения и т. д.). Причина, по которой он является единичным, заключается в том, что, как я уже сказал, класс автоматически инициализирует стороннюю библиотеку; это происходит при создании одноэлементного объекта и выходит из библиотеки при его уничтожении.
В любом случае, чтобы GR мог отображать Шрифт , он, очевидно, должен иметь доступ к своему FontData объекту. Одним из вариантов может быть использование общедоступного метода получения, но при этом будет реализована реализация Font (никакой другой класс, кроме Font и GR , не должен заботиться о FontData ). Вместо этого я подумал, что лучше сделать GR другом Font .
Примечание: до сих пор я делал две вещи, которые некоторые могут считать неясными (синглтон и друг), но я не хочу об этом спрашивать. Тем не менее, если вы считаете, что мое обоснование превращения GR в синглтон и друга Font неверно, пожалуйста, критикуйте меня и, возможно, предложите лучшие решения.
Тенистая вещь. Итак, GR имеет доступ к Font :: data _ хотя и дружба, но как он точно знает, что такое FontData есть (так как он не определен в заголовке, это неполный тип)? Я просто покажу код и комментарий с обоснованием ...
// =============================================================================
// graphics/font.cpp
// -----------------------------------------------------------------------------
struct Font::FontData
: public sf::Font
{
// Just a synonym of sf::Font
};
// A redefinition of FontData exists in GraphicsRenderer::printText(),
// which will have to be modified as well if this definition is modified.
// (The redefinition is called FontDataSurogate.)
// Why not have FontData defined only once in a separate header:
// If the definition of FontData changes, most likely printText() text will
// have to be altered also regardless. Considering that and also that FontData
// has (and should have) a very simple definition, a separate header was
// considered too much of an overhead and of little practical advantage.
// =============================================================================
// graphics/graphics_renderer.cpp
// -----------------------------------------------------------------------------
void GraphicsRenderer::printText(const Font& fnt /* ... */)
{
struct FontDataSurogate
: public sf::Font {
};
FontDataSurogate* suro = (FontDataSurogate*)fnt.data_.get();
sf::Font& font = (sf::Font)(*suro);
// ...
}
Так вот, это темная вещь, которую я пытаюсь сделать. По сути, мне нужен обзор моего обоснования, поэтому , пожалуйста, скажите мне, если вы думаете, что я сделал что-то ужасное или если не подтвердите свое обоснование, чтобы я мог быть немного увереннее, я делаю правильные вещи , :) (Это мой самый большой проект, и я только начинаю, поэтому чувствую, что все в темноте.)