如何重构此代码层次结构(与Demeter法则相关)

时间:2009-01-22 05:00:24

标签: c++ oop refactoring law-of-demeter

我有一个游戏引擎,我将物理模拟与游戏对象功能分开。所以我为身体提供了一个纯粹的虚拟课程

class Body

我将从中推导出物理模拟的各种实现。我的游戏对象类看起来像

class GameObject {
public:
   // ...
private:
   Body *m_pBody;
};

我可以插入我特定游戏所需的任何实现。但是,当我只有Body时,我可能需要访问所有GameObject函数。所以我发现自己写了很多像

这样的东西
Vector GameObject::GetPosition() const { return m_pBody->GetPosition(); }

我很想抓住所有这些,只做像

这样的事情
pObject->GetBody()->GetPosition();

但这似乎是错误的(即违反了得墨忒耳法则)。此外,它只是将实现的冗长性推向了使用。所以我正在寻找一种不同的方式。

4 个答案:

答案 0 :(得分:3)

您可以采取的一种方法是将Body接口拆分为多个接口,每个接口具有不同的用途,并且仅对其必须公开的接口赋予GameObject所有权。

class Positionable;
class Movable;
class Collidable;
//etc.

具体的Body实现可能会实现所有接口,但只需要公开其位置的GameObject只能引用(通过依赖注入)Positionable接口:

class BodyA : public Positionable, Movable, Collidable {
    // ...
};

class GameObjectA {
private:
    Positionable *m_p;
public:
    GameObjectA(Positionable *p) { m_p = p; }
    Positionable *getPosition() { return m_p; }
};

BodyA bodyA;
GameObjectA objA(&bodyA);

objA->getPosition()->getX();

答案 1 :(得分:3)

Demeter定律的概念是你的GameObject不应该具有像GetPosition()这样的函数。相反,它应该具有MoveForward(int)TurnLeft()函数,可以在内部调用GetPosition()(以及其他函数)。基本上他们将一个界面翻译成另一个界面。

如果你的逻辑需要一个GetPosition()函数,那么将它转换为一个接口a Ates Goral是有道理的。否则你需要重新思考为什么你如此深入地抓住一个对象来调用它的子对象上的方法。

答案 2 :(得分:1)

游戏层次结构不应涉及大量继承。我不能指向任何网页,但这是我从几个来源收集的感觉,最着名的是游戏宝石系列。

您可以使用ship-> tie_fighter,ship-> x_wing等层次结构。但不是PlaysSound-> tie_fighter。你的tie_fighter类应该由它所代表的对象组成。物理部件,图形部件等。您应该提供一个与游戏对象进行交互的最小界面。在引擎或物理部件中实现尽可能多的物理逻辑。

通过这种方法,您的游戏对象将成为更基本的游戏组件的集合。

所有这一切,您将希望能够在游戏事件期间设置游戏对象物理状态。因此,您最终会遇到设置各种状态的问题。它只是icky但这是我迄今为止找到的最好的解决方案。

我最近尝试使用Box2D的想法制作更高级别的状态函数。有一个功能SetXForm用于设置位置等。另一个用于SetDXForm的速度和角速度。这些函数将代理对象作为表示物理状态的各个部分的参数。使用这些方法可以减少设置状态所需的方法数量,但最终你可能最终仍然会实现更细粒度的方法,而代理对象的工作量会超过你跳过的方法。关于一些方法。

所以,我没那么帮忙。这更像是对前一个答案的反驳。

总之,我建议你坚持使用多种方法。游戏对象和物理对象之间可能并不总是存在简单的一对一关系。我们遇到了一个让一个游戏对象代表爆炸中的所有粒子要简单得多的地方。如果我们已经放弃并暴露了一个体指针,我们就无法简化问题。

答案 3 :(得分:0)

我是否理解你正在将某物的物理性与其游戏表现分开?

即,你会看到这样的事情:

class CompanionCube
{
    private:
        Body* m_pPhysicsBody;
};

如果是这样,那对我来说有点不对劲。从技术上讲,你的'GameObject'一个物理对象,因此它应该来自Body。

听起来你正在计划交换物理模型,这就是你试图通过聚合来实现它的原因,如果是这样的话,我会问:“你打算在运行时交换物理类型,还是编译时间?“。

如果编译时间是你的答案,我会从Body派生你的游戏对象,并使Body成为你想要的默认物理体的typedef。

如果是运行时,你必须编写一个“Body”类来进行内部切换,如果你的目标是使用不同的物理学,这可能不是一个坏主意。

或者,您可能会发现根据游戏对象的类型(水,刚体等),您将拥有不同的“父”类,因此您可以在推导中明确说明。

无论如何,我会停止漫无边际,因为这个答案是基于很多猜测。 ;)如果我不在基础,请告诉我,我会删除我的答案。