组织类层次结构的最佳方法,包括可覆盖的“更新”功能

时间:2009-01-16 23:21:23

标签: c++

我有一个基类“Foo”,它有一个Update()函数,我想为每个类的每个实例调用一次。给定一个名为“foo”的类的对象实例,然后每帧一次,我将调用foo-> Update()。

我有一个从我的基类派生的类“Bar”,它也需要更新每一帧。

我可以给派生类一个Update()函数,但是我必须记得调用它的base :: Update()函数 - 没有什么强制要求我调用base :: Update()函数,因为我已经覆盖它,并且很容易忘记(或选择不)调用基数:更新功能。

作为替代方案,我可以为基类提供一个受保护的OnUpdate()函数,该函数可以重写,并从base :: Update()函数调用它。这消除了我记得从派生的更新函数调用base :: Update()的责任,因为我不再覆盖它。名为“bar”的Bar实例将调用bar-> Update();这将首先调用基类的Update()方法,该方法将依次调用覆盖的OnUpdate()函数,执行派生类的必要更新。

这解决了一切。除了。如果我想从“Bar”类派生另一个可更新类,那该怎么办呢?

Baz(源自Bar)也有更新要求。如果我把它们放在Baz的OnUpdate()函数中,我会回到原来的问题,因为我必须记得告诉Baz的OnUpdate()函数调用Bar的OnUpdate()函数,否则Bar的OnUpdate()函数就不会被叫。

实际上,我希望Bar的OnUpdate()函数是不可覆盖的,而是在它完成所需的任何操作之后调用一个可覆盖的函数,可能称为OnUpdate2()...

如果我想推出另一个班级? OnUpdate3? OnUpdate4?更新后?

有更好的方法吗?


进一步信息:

我的具体问题域是一个3d世界。我已经确定我的基类是一个“定位器”(一个具有位置和方向的对象)。

我的第一个派生类是“PhysicsObject” - 一个也有质量,速度,碰撞信息等的定位器。

我的下一个派生类是一个“Camera” - 它派生自PhysicsObject。除了位置和速度,它还有关于视口,景深等的信息。


MattK建议简化层次结构 - 如果从未引用定位器,则将其合并到PhysicsObject中。

我也在思考如何将布局颠倒过来并使用构图而不是继承。

也许相机有物理属性。 也许PhysicsObject是一个位置。

我将不得不考虑更多关于这个问题。


我喜欢Uri的方法:“遵守合同。”这是规则 - 请遵循它。 Uri是正确的,无论我试图放入什么样的保护措施,任何人都可以绕过它们,所以也许在这种情况下,最简单的解决方案是最好的。我的所有update()函数都需要调用它们的base :: update()函数。

感谢大家的帮助!

3 个答案:

答案 0 :(得分:3)

听起来像是想要合成而不是继承。如果有一个接口IUpdateable,并且Foo拥有一组IUpdateable对象,并且每个tick都调用一个Update方法怎么办?然后Bar和Baz可以实现更新;你唯一担心的是如何最好地用Foo注册它们。

根据您的进一步信息:您可能希望将主要对象视为与PhysicsObject类似,并使用合成来包含实现特定行为的对象,例如Camera对象的对象。

答案 1 :(得分:2)

这是一个很好的问题,我已经多次遇到过它。

不幸的是,目前我还没有像C ++这样的主流语言熟悉的语言机制,尽管我希望(至少在未来)Java可以使用注释。

我使用了各种技术,包括你提出的建议,各有利弊。顽固的方法并不总是值得的。

我今天的观点是,如果你真的需要使用继承(而不是组合),就像在这里听到的那样,那么最好选择优雅的解决方案并遵守书面合同。我承认,这很危险,但其他方法也存在风险。

一般来说,人们在阅读被覆盖方法的文档时要比他们使用的方法更仔细。换句话说,虽然你想避免让你的班级用户“惊讶”,并且不能指望他阅读文档,但在继承的情况下,你可以多计算一点,特别是如果你是唯一的用户

如果您正在展示API函数并且您希望许多其他人覆盖您的子类,您可以进行各种健全性检查以确保调用该方法,但最后,您必须依赖合同,正如许多标准库类一样。

答案 2 :(得分:1)

我认为你想要的东西不容易用类层次结构来实现。

一种可能的解决方案是使用处理信号/插槽的库(我使用sigslot http://sigslot.sourceforge.net/)。

在基类中声明一个信号。

class Base : has_slots<> {
  public:
    Base() { SignalUpdate.connect(this, &Base::OnUpdate); }
    void Update() { SignalUpdate.emit(); }
    void OnUpdate() { cout << "Base::OnUpdate" << endl; }
  private:
    signal0<> SignalUpdate;
};

现在,在每个“派生”类中,您可以使用自己的方法

连接此类信号
class Derived : public Base {
  public:
    Derived() { SignalUpdate.connect(this, &Derived::OnDerivedUpdate); }
    void OnDerivedUpdate() { cout << "Derived::OnDerivedUpdate" << endl; }
};

(请注意,此类不再需要派生自Base)。

现在每次调用Update时,都会调用所有连接的方法。

还有其他框架实现了类似的行为:boost信号,qt slot,libsigc ++。您应该尝试看看这些是否符合您的需求。