Добрый день, уважаемые эксперты.
Решил написать сюда, а не в проектирование. Павел рассудит
, правильно ли это было. Прошу прощения за длинный топик.
// game_object.hpp
// Корень иерархии игровых объектов.
// Все производные от него автоматом становятся видны менеджеру и "умеют" обновляться
class game_object {
public:
game_object() { manager::instance()->reg_object(this); }
virtual ~game_object() { manager::instance()->unreg_object(this); }
virtual void update() = 0;
};
// manager.hpp
// Менеджер - заправИло всех объектов.
// Тута они регистрятся при рождении, анрегистрятся при смерти
class manager {
public:
typedef std::set<game_object*> objects_queue;
static manager* instance() { return instance_ ? instance_ : instance_ = new manager(); }
void reg_object(game_object* go) { objects_.insert(go); }
void unreg_object(game_object* go) { objects_.erase(go); }
protected:
manager() { }
manager(const manager&) { }
objects_queue objects_;
private:
static manager* instance_;
};
// manager.cpp
manager* manager::instance_ = 0;
Ограничения:
1)
не все, а лишь некоторые игровые объекты подлежат отрисовке и должны обладать методом draw();
2)
не все, а лишь некоторые игровые объекты подлежат сериализации и должны обладать методами load()/save();
3) все объекты должны хранится в одной очереди, а то получится отдельная очередь для тех, кто апдейтится, отдельная — для тех кто рисуется, уродство одним словом.
Оказалось, это непросто
Полезнейшими функциями менеджера были бы "массовые действия" над всем контейнером objects_, типа serialize_all(), draw_all(). Но ведь в objects_ хранятся указатели на game_object, а, значит. вызов любой функции окромя update() невозможен.
Сразу отбросим очевидные ереси типа сохранения в объекте "кода типа".
Но остальные решения, которые я придумал, кажутся мне не менее корявыми.
1. dynamic_cast
Лепим производные от game_object классы, обладающие необходимым интерфейсом, типа
// serializable.hpp
class serializable : public game_object {
public:
virtual void save() = 0;
virtual void load() = 0;
};
На этапе выполнения проверяем возможность вызова нужной функции
// manager.hpp
class manager {
public:
/* ... */
void save_all() {
for (objects_queue::iterator it = objects_.begin(); it != objects_.end(); ++it) {
if (dynamic_cast<serializable*>(*it)) (*it)->save();
}
}
/* ... */
};
2. Передача менеджерам сообщений объектам
Менеджер посылает объекту (неизвестного ему динамического типа) сообщение (к примеру, элемент енума);
// manager.hpp
enum command { UPDATE, DRAW, SAVE, LOAD, };
class manager {
public:
/* ... */
void send(command c) {
for (objects_queue::iterator it = objects_.begin(); it != objects_.end(); ++it) {
(*it)->execute(c);
}
}
/* ... */
};
А уже объект определяет, в состоянии ли он выполнить запрос
// game_object.hpp
class game_object {
/* ... */
virtual void execute(command c) {
if (c == UPDATE) update();
}
/* ... */
};
// serializable.hpp
class serializable : public game_object {
public:
virtual void save() = 0;
virtual void load() = 0;
virtual void execute(command c) {
game_object::execute(c);
if (c == SAVE) save();
else if (c == LOAD) load();
}
};[/b]
Вопросы:
1) Какие из упомянутых ограничений кажутся нелогичными?
2) Существует ли "красивый" (а еще лучше стандартный) способ решения этой проблемы?
3) Какое решение из предложенных предпочтительнее?
Приветствуются любые варианты.