【问题标题】:How can I manage a group of derived but otherwise Unrelated Classes如何管理一组派生但不相关的类
【发布时间】:2010-12-15 17:12:14
【问题描述】:

似乎我越是谈论这个问题,我就越了解它。我认为我之前的问题没有传达我想要正确做的事情。对此我深表歉意。

在我的设计中,我的游戏对象本质上是一个聚合类,游戏对象中的所有功能都是通过向其中添加各种“功能”来实现的。要素是要素类的子类,具有自己的成员和功能。所有功能都可以接收消息

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 以接收来自它们的消息,Features 可以订阅 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 类将收到一条消息,它可以通过它的 ID 判断是 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 部分?

【问题讨论】:

    标签: c++ design-patterns oop aggregation


    【解决方案1】:

    另一个答案是编辑太臃肿,所以我开始了一个新的。

    您在 receiveMessage() 函数中进行的转换绝对是代码异味。

    我认为您需要使用以下组合:

    这个想法是每个组件类型将只订阅它自己类型的消息,因此只会接收为其指定的消息。这应该消除了铸造的需要。

    例如,通知对象可以使用由消息 ID 索引的通知对象向量。观察对象(派生的组件类)可以订阅由其自己的消息 ID 索引的特定通知器。

    您认为这种设计模式会有所帮助吗?

    【讨论】:

    • 我觉得和我的想法比较接近,唯一能想到的问题是VisualComponents可以接收各种消息。例如,移动消息(玩家移动时发送的消息)对于许多组件来说都是有趣的,用于更新精灵位置的视觉,用于更改声音的 3d 位置的声音,诸如此类。我同意铸造是一种代码气味。
    • 更新了我的问题,我认为我没有正确传达我想要做的事情。
    【解决方案2】:

    我不确定我是否真的理解你的问题,我认为你需要澄清你想要实现的更多目标。

    不过还有一些其他的 cmets。

    我不认为公共继承(正如您已经实现的那样)是在这里使用的最佳设计模式。公共继承的黄金法则是,只有在派生类真正“是”类的对象时才应该使用它。

    在 C++ 中使用继承的主要好处之一是实现 多态性,其中(例如)你有一个指向 Base 对象的指针列表,你可以调用这些对象的方法,它们视情况分派给相关的VisualComponentPhysicsComponent 对象方法。

    因为(用你的话来说)它们有“不相关的类接口”,你不会得到多态性的任何好处。

    听起来你真的是从Base 类继承来实现Mixin 模式。

    也许组合是更好的方法,您可以在 VisualComponentPhysicsComponent 类中包含 Base 类的副本(您必须重命名)。

    但是,基于以下问题:

    如果我只有一个引用或指针 以我有哪些设计选项为基础 暴露接口 VisualComponent 还是 PhysicsComponent?

    GameObject 类(您在 main() 中实例化的)不是已经为您执行此操作了吗?


    编辑:

    好的,我想我现在已经更好地理解了这个问题已经被编辑了。

    但我需要一些方法来存储所有 动态的组件 GameObject 但仍然可以使用 它们各自的界面。

    我可以看到这个工作的唯一简单方法是在Base 中创建一个virtual 方法,该方法在每个派生类中被覆盖并实现类特定的行为。 GameObject 可以简单地存储Base 指针的容器并调用将被分派到派生类的virtual 方法。

    我还建议创建Render()Move() 和任何非虚拟方法private,以便GameObject 类只能访问公共(virtual)方法。这有助于保持公共界面清洁。

    我不确定这是否有帮助。


    编辑 2:

    在 cmets 中进一步讨论后,听起来factory patternabstract factory pattern 是您所需要的。

    【讨论】:

    • 这个设计背后的想法是我可以通过添加各种组件对象来专门化游戏对象,而不必为每个可能的组件组合创建一个特定的类,因为组件的数量可能会扩大得多然后只是视觉和物理。
    • 我不确定您打算如何避免为每个可能的 Component 组合创建一个特定的类。如果它们每个都有唯一的属性(成员数据)和行为(成员方法),我认为每个都需要在一个单独的类中。
    • 我在回答中添加了更多内容 - 如果有帮助,请告诉我。
    • 我可以看到多态方法是如何工作的我希望我可以避免这种特定的解决方案,因为每个组件之间的接口可能有多么不同,我希望会有另一种解决方案。如果说 BaseComponent 的 ID 为 1(将其标记为 VisualComponent),则可以让我从 BaseComponent 生成一个 VisualComponent 类
    • 工厂模式可能是最好的方法,或者抽象工厂模式:en.wikipedia.org/wiki/Abstract_factory_pattern
    【解决方案3】:

    访客模式。如果我明白你在问什么。

    虽然确实需要了解更多上下文!

    【讨论】:

      【解决方案4】:

      看看boost.signals

      您可以为每种消息类型定义一个信号,并允许功能向其添加槽(接收器),这可能是它们的任何名称的成员函数,或任何其他具有适当签名的可调用事物。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-05-04
        • 1970-01-01
        • 2015-01-30
        • 1970-01-01
        • 1970-01-01
        • 2012-03-05
        相关资源
        最近更新 更多