在我的Java Game项目中过度使用static?

我目前正在开发一个Java平台,我为它编写了自己的游戏引擎Bonsai 。 现在我问自己一个问题“我是否过度使用静力学?”。

一方面它非常方便,因为我不必像地图或玩家那样在每个类中保持对游戏实例的引用。 另一方面……我已经不得不剥离applet支持,因为那里的所有静态内容都非常错误。

所以我的问题是,既然你可能比我更有经验的Java程序员,我应该摆脱所有静态吗? 如果是的话,什么才能成为这样的有效方法:

public void draw(Graphics2D) { if (this.game.time() > this.timer) { this.game.image.draw(this.tiles[this.game.animation.get("tileAnim")], x, y, null) } } 

代替:

 public void draw(Graphics2D) { if (Game.time() > this.timer) { Image.draw(this.tiles[Animation.get("tileAnim")], x, y, null) } } 

甚至在地图编辑器中更糟糕:

  public void control() { if(this.map.game.input.keyPressed(...)) { this.map.game.sound.play(...); } } 

编辑
根据答案,我决定使用GameObject类为每个组件提供包装器方法。 地图,播放器等然后从它inheritance,这样我所有this.game调用都隐藏在场景后面,它在前端看起来仍然很好看:

 public class GameObject { private Game game; public GameObject(Game g) { game = g; } public Game Game() { return game; } public GameAnimation Animation() { return game.animation; } public GameInput Font() { return game.input; } // ... public long Time() { return game.time(); } } 

现在代码看起来像这样:

 public class Player() { public Player(Game g, int xpos, int ypos) { super(g); // do other stuff } public void jump() { // jump code Sound().play("jump"); } } 

或者这是更糟糕的Java?

EDIT2
好吧我已经遇到问题使用方法调用编译器给我错误,因为它无法在原始的方法中找到我的子类游戏的方法,我想我会在这里使用普通字段。

EDIT3
好吧,我的GameObject类现在看起来像这样,一切正常,我可以重新实现applet支持:)

 public class GameObject { protected Game game; protected GameAnimation animation; protected GameFont font; protected GameInput input; protected GameImage image; protected GameSound sound; public GameObject(Game g) { game = g; animation = game.animation; font = game.font; input = game.input; image = game.image; sound = game.sound; } } 

首先。

你不必去掉所有的静态代码,因为那会使它在“纸上”变得更好。

你真的必须要了解实例代码(非静态)和类代码(静态)之间的区别

当方法/属性不需要类的实例工作时,使用静态代码(类方法/属性)。 一个很好的例子是图像绘制方法: Image.draw()

实例方法/属性对于保持给定对象的状态非常有用,该状态必须与其他对象中的数据分开。

例如,如果你的游戏中有Player类,并且你有两个实例player1player2那么每个实例都有自己的得分:

  public class Player { private int score; private String name; etc..... } Player one = new Player("Player 1"); display( one.score ); Player two = new Player("Player 2"); display( two.score ); 

而不是必须创建工件来保持每个玩家得分(比如将它们放在每个索引都是属性的数组中,并使该数组静态等等)

其次

您可以通过为对象分配适当的属性并以正确的方式执行封装来减少您提到的构造object1.atr2.other.next.etc

如果对象b需要访问另一个a第N个元素,则很可能所述属性属于对象b而不是或者可能是该对象a应该提供避免暴露其内部的方法。

它甚至使代码更容易阅读:

即。

 public void draw(Graphics2D) { if( this.game.needsDrawing() ) { this.game.draw(); } } 

代替:

 public void draw(Graphics2D) { if (this.game.time() > this.timer) { this.game.image.draw(this.tiles[this.game.animation.get("tileAnim")], x, y, null) } } 

同样,它取决于具体情况,可能存在您不需要实例的情况(再次,像Image的draw()实用工具方法)

最后。

实例方法允许您使用多态,而类方法则不允许(至少在Java和其他静态类型语言中)。

因此,如果您的代码是实例代码,您可能会受益于使用运行时委派和多态。 例如,如果所有代码都是静态的,则不能使用它的状态模式 ,但您可以使用实例代码:

 class Game { GameState state = GameState.getInitialState( this ); void run() { while( state.alive ) { do XYZ state.updateState(); } } } class GameState { Game context; static GameState getInitialState( Game g ) { return new StartGameState(g); } void updateState(); } class StartGameState { void updateState() { if( this.context.someCondition() ) { this.context.state = new MidGameState(); } } } class MidGameState { void updateState() { if( this.context.someOtherCondition() ) { this.context.state = new EndGameState(); } } } class EndGameState { void updateState() { Game over... } } 

再一次,只有在面向对象时才有意义,就像对象具有需要数据的属性一样? 如果不是,保持该部分代码静态可能是好的。

所有这些概念(封装,多态,抽象,inheritance等)都是OO技术的本质,并且在OOA / D中有所体现,虽然它们看起来像语法糖(并且大部分时间都是这样),但是当您使用时,您的体验会告诉您应该有代码和什么时候作为实例代码。

static作为一种语义工具存在于任何其他工具中。 当某些东西是静态的时,这对于该类试图建模的内容具有意义 。 在你的情况下,你有效地使用静态来创建全局存储,你显然遇到了一些问题,即全局变量的一般规则是“坏”。

如果你的静态接口的行为以你不想要的方式强迫你处理设计问题和实现问题(比如你的applet支持),那么是的,这是一个问题,你应该考虑重构为更合适的设计。

你应该摆脱所有的静电吗? 也许。 如果在任何时候都不可能存在多个特定对象的单个实例,并且您愿意处理同步对象访问的成本,则可能某些东西可能保持静态。 我在你的例子中没有看到任何东西,我会根据我对他们所做的事情的猜测而绝对保持静态。

(关于你对链式对象引用的明显厌恶,你可能想要研究Demeter法则和可以使代码更符合它的技术。我认为代码不应该严格遵守它,但通常应该有试图保持链式方法的调用有点短。)

在我看来,使用静态来共享一些全局状态而不必将其注入到客户端类中会使代码更难以测试,重用,破解等,因为您这些客户端类耦合到他们正在使用静态成员的类。

这是一个人为的例子:

如果你想出两种不同类型的游戏类并且你想要对它们进行基准测试,那该怎么办呢?

或许你想扩展你的引擎来运行两种完全不同类型的游戏(可能是一个平台游戏和一个射击游戏,谁知道),你可以通过修改游戏和保持所有其他类来实现这一点。相同。 (我说它是人为的。)

在所有其他类访问Game类的静态成员时,您必须编写应用程序的2个版本,每个版本都有自己的Game实现。

如果您只是将Game对象传递到需要它的每个类中,您可以将Game子类化并将任何类型的Game传递到需要它的客户端类中。

过度使用静态通常会加剧对代码的更改并降低其可测试性。

考虑用普通对象替换静态对象。 您可以将它们传递给对象的构造函数。 这样,它们很容易替换为另一个实现(真实或模拟/存根进行测试)。 这种技术称为dependency injection(DI) 。

例如,您已将三个对象传递给您的应用:

  • gameTimer(从Game类中提取,使其不像神一样)
  • 图片
  • 动画

并将其保存到字段中。 这种方式代替

 public void draw(Graphics2D) { if (Game.time() > this.timer) { Image.draw(this.tiles[Animation.get("tileAnim")], x, y, null) } } 

你会的

 public void draw(Graphics2D) { if (this.gameTimer.time() > this.timer) { this.image.draw(this.tiles[this.animation.get("tileAnim")], x, y, null) } } 

差异看起来很微妙,但实际上很重要,因为您的代码将变得更加模块化。

  • 每个对象都有单一的责任,因此可以很好地进行测试。
  • 如果您要实现另一个版本的Animation类(Animation2),则只需要在main()函数中进行更改,而不是在Animation类中进行更改。
  • 单击一下您的代码就可以看到使用了哪些对象,无需查找静态方法调用。

我对静力学的个人经验实际上是双重的。 首先,我发现静态是一种反模式,经常混淆和隐藏我的设计有问题的事实。 如果我发现自己需要一个静态变量,我必须问自己为什么。 但是,有时需要静电,实际上是合适的。 在这些情况下,我试图将真正全局的部分与程序隔离开来,并将它们放在自己的单例类中,而不是某处的静态变量。 虽然它可能只是语义,但是根据我的经验,将它们变成单身可以让事情在OO意义上更加清晰。