Как я могу управлять группой производных, но никак не связанных классов - PullRequest
1 голос
/ 12 ноября 2009

Кажется, чем больше я говорю об этой проблеме, тем лучше я ее понимаю. Я думаю, что мой предыдущий вопрос не передал то, что я пытаюсь сделать правильно. Мои извинения за это.

В моем дизайне у меня есть GameObjects, которые по сути являются классом агрегации, все функциональные возможности в GameObject реализованы путем добавления к нему различных «функций». Функция - это подкласс класса Feature, который имеет свои собственные члены и функции. Все функции могут получать сообщения

class Feature
    {
    public:
        virtual void takeMessage(Message& message) = 0;
    };

class VisualFeature : public Feature
    {
    public:
        void takeMessage(Message& message);
    private:
        RenderContext m_renderer;
    };

... Additional Features ...

FeatureServers - это объекты, которые отвечают за координацию различных функций. GameObjects может подписываться на FeatureServers для получения сообщений от них, а Feature может подписываться на GameObjects для обработки сообщений, которые ему интересны.

Так, например, в этом коде:

GameObject Square;
VisualFeature* SquareSprite = new VisualFeature();
Square.subscribe(SquareSprite, "MESSAGE_RENDER");
Square.addFeature(SquareSprite);
m_VisualFeatureServer.subscribe(Square, "MESSAGE_RENDER");

VisualFeatureServer отправляет сообщение, привязанное к «MESSAGE_RENDER», которое может выглядеть примерно так

class Message
    {
    public:
        std::string getID() {return m_id;}
        bool isConsumed() {return m_consumed;}
        void consume() {m_consumed = true;}
    protected:
        bool isConsumed;
        std::string m_id;
    }

class Message_Render : public Message
    {
    public:
        Message_Render() : m_id("MESSAGE_RENDER"), m_consumed(false) {}
        RenderTarget& getRenderTarget() {return m_target;}
    private:
        RenderTarget& m_target;
    };

Когда VisualFeatureServer отправляет класс Message_Render в Square GameObject, он затем перенаправляет его всем компонентам FeatureComponents, которые подписаны для получения этого конкретного сообщения. В этом случае класс VisualFeature получает сообщение Message_Render. Вот где моя проблема: класс VisualFeature будет получать сообщение, и он может сказать, что это Message_Render по его идентификатору, я хочу иметь возможность рассматривать его как Message_Render, а не как сообщение вроде:

void VisualFeature::takeMessage(Message& message)
    {
    //Here's the problem, I need a pattern to handle this elegantly
    derivedMessage = convertMessageToDerivedType(message);
    this->handleDerivedMessageType(derivedMessage);
    }

void VisualFeature::handleDerivedMessageType(Message_Render& message)
    {
    message.getRenderTarget().render(m_renderer);
    message.consume();
    }

Есть ли способ элегантно разобраться с частью takeMessage этого дизайна?

Ответы [ 4 ]

1 голос
/ 12 ноября 2009

Я не уверен, что я действительно понимаю ваш вопрос, и я думаю, что вам нужно уточнить, чего вы пытаетесь достичь больше.

Только несколько других комментариев.

Я не думаю, что публичное наследование (как вы реализовали) является лучшим шаблоном проектирования для использования здесь. Золотое правило с публичным наследованием заключается в том, что его следует использовать, только если производный класс действительно "является" объектом класса base .

Одним из основных преимуществ использования наследования в C ++ является реализация полиморфизма , где (например) у вас есть список указателей на Base объекты, и вы вызываете методы для этих объектов, и они отправляется соответствующим объектным методам VisualComponent и PhysicsComponent в зависимости от ситуации.

Поскольку (по вашим словам) они имеют "несвязанные интерфейсы классов", вы не получите никаких преимуществ от полиморфизма.

Похоже, вы действительно наследуете от класса Base для реализации шаблона Mixin .

Может быть, композиция - лучший подход, когда вы включаете копию класса Base (который вам придется переименовать) в класс VisualComponent или PhysicsComponent.

Однако, исходя из следующего вопроса:

Если у меня есть только ссылка или указатель Основать, какие варианты дизайна у меня есть выставить интерфейс VisualComponent или PhysicsComponent?

Разве класс GameObject (который вы создаете в main()) уже делает это для вас?


Edit:

Хорошо, я думаю, теперь я понимаю, что вопрос отредактирован.

Но мне нужен какой-то способ хранить все Компоненты динамически в GameObject, но все еще можно использовать их индивидуальные интерфейсы.

Единственный простой способ увидеть эту работу - создать метод virtual в Base, который переопределяется в каждом производном классе и реализует поведение, специфичное для класса. GameObject может просто хранить контейнер с указателями Base и вызывать метод (ы) virtual, который будет отправлен производным классам.

Я бы также порекомендовал сделать Render(), Move() и любые не виртуальные методы private, чтобы класс GameObject мог получить доступ только к общедоступным (virtual) методам. Помогает поддерживать общедоступный интерфейс в чистоте.

Я не уверен, поможет ли это.


Редактировать 2:

После дальнейшего обсуждения в комментариях звучит так: фабричный шаблон или абстрактный фабричный шаблон - это то, что вам нужно.

1 голос
/ 12 ноября 2009

Другой ответ был слишком раздутым с изменениями, поэтому я начал новый.

Приведение, которое вы выполняете в функциях receiveMessage(), определенно является запахом кода.

Я думаю, вам нужно использовать комбинацию:

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

Уведомляющий объект может, например, использовать вектор объектов уведомителя, проиндексированных идентификатором сообщения. Наблюдающий объект (производный класс компонента) может подписаться на определенный уведомитель, проиндексированный по его собственному идентификатору сообщения.

Как вы думаете, этот шаблон дизайна поможет?

0 голосов
/ 12 ноября 2009

Взгляните на boost.signals

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

0 голосов
/ 12 ноября 2009

Шаблон посетителя. Если я понимаю, что вы спрашиваете.

Хотя действительно нужно знать больше контекста!

...