结合具有不同字段效率的子类?

时间:2010-02-07 22:50:48

标签: java android oop

我正在开发Java Android游戏。这里的游戏通常需要使用内存池来避免垃圾回收。

为了争论,我说我有以下几个类:

// Enemy in the game
class Enemy { int x; int y; abstract void update(); }
// Enemy that just bounces around
class Roamer extends Enemy { int direction; ... }
// Enemy that chases a player
class Chaser extends Enemy { Player target; ... }
// maybe 20 more enemy types...

在您需要使用内存池时,使用子类是一件很痛苦的事。说你想要多少Roamer和Chaser对象,而不仅仅是你可能需要多少敌人对象。此外,虚拟函数调用更新可能很慢。

我想到的一个替代方案是将所有这些类合并为一个例如

class Enemy
{
    int direction;
    Player target;
    int type; // i.e. ROAMER_TYPE, CHASER_TYPE
}

然后我会有一个更新函数,它检查“type”变量并相应地更新。这显然是一种更像C的方法。但是感觉很酷,因为例如漫游者敌人将拥有一个永远不会使用的“目标”变量。我不太可能在内存中同时拥有超过100个敌人,所以它确实不是一个很大的记忆。我只想在好的代码和速度之间做一些妥协。

有没有人对如何最好地构建这个有任何意见?合并这样的课程走得太远了吗?这是一个好主意吗?我应该只使用常规类树吗?

我知道没有正确的答案,但我想要一些不同的观点。

4 个答案:

答案 0 :(得分:2)

这里的作文会比继承更好吗?例如,像这样:

class Enemy {
    int direction;
    Player target;
    Strategy updater;
}
interface Strategy {
    void update(Enemy state);
}
class Roamer implements Strategy {
    public void update(Enemy state) {
        //Roamer logic.
    }
}
class Chaser implements Strategy {
    public void update(Enemy state) {
        //Chaser logic.
    }
}

然后,您只需要每个特定策略的一个实例。例如,来自池中的所有Enemy对象当前充当Roamer将使用单个Roamer实例进行更新。这可以防止必须将所有逻辑保留在一个类中,这可能会变得非常复杂。

如果这不是Android游戏,那么你可以在Enemy类中放置一张地图,以保持状态为一个策略所需的敌人,但另一个策略不需要。因为我们必须避免可以触发垃圾收集器的分配,所以最好像你说的那样拥有Enemy类中所需的所有成员。

你可以在Enemy类中使用preallocated数组来表示状态。例如,追逐者将始终保持对其在状态数组的特定索引中追逐的播放器的引用。然而,这变得非常复杂。

答案 1 :(得分:1)

  

此外,虚拟函数调用更新可能很慢。

无意义!进行虚函数调用的成本是JIT编译应用程序中的几个指令。即使在解释模式下,我也只希望它能更多地使用十几个原生指令。这是噪音......除非你每秒钟拨打这些电话数千万次。

我会赞同@ Laurence的回答。一旦您验证(即通过测量)确实需要解决的性能问题,只会花费时间在性能黑客上。如果忽略这一点,就有可能使代码更复杂,维护更难以维护。并且你可能会在天真的尝试中使性能变得更糟,尤其是当Android JIT编译器变得更好时。

答案 2 :(得分:0)

您是否确实在没有内存池的情况下确认了性能问题?

假设您确实需要使用内存池,为什么不为每种类型的对象使用一个内存池并让它们“增长”到您需要的对象数量?如果对象类型之间的平衡随着时间的推移而发生剧烈变化,则效率低下,但您描述的方法也存在效率低下(未使用的字段),并且似乎维护起来并不好玩。

答案 3 :(得分:0)

您可以从此处复制框架内部的内存池(请参阅Pool,Poolable,PoolableManager,Pools,FinitePool,SynchronizedPool):

https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/util

如果您这样做,请务必将它们放在您自己的包名称空间中。

为了对象池,不需要丢失面向对象。如果那里的代码仍然如此通用以至于让您感到害怕,请使用相同的想法为您的对象执行简化的通用池。