"Хитрый" проход по контейнеру полиморфных элементов.
От: Alxndr Германия http://www.google.com/profiles/alexander.poluektov#buzz
Дата: 18.01.04 15:59
Оценка:
Добрый день, уважаемые эксперты.
Решил написать сюда, а не в проектирование. Павел рассудит , правильно ли это было. Прошу прощения за длинный топик.

// 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
  1. Лепим производные от game_object классы, обладающие необходимым интерфейсом, типа
    // serializable.hpp
    class serializable : public game_object {
    public:
        virtual void save() = 0;
        virtual void load() = 0;
    };

  2. На этапе выполнения проверяем возможность вызова нужной функции
    // 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. Передача менеджерам сообщений объектам
  1. Менеджер посылает объекту (неизвестного ему динамического типа) сообщение (к примеру, элемент енума);
    // 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);
            }
        }
        /* ... */
    };

  2. А уже объект определяет, в состоянии ли он выполнить запрос
    // 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) Какое решение из предложенных предпочтительнее?
Приветствуются любые варианты.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.