我目前正在开发一个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类为每个组件提供包装器方法。地图,播放器等然后从它中继承,这样我所有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;
}
}
答案 0 :(得分:9)
首先。
你不必去除所有的静态代码,因为这会使它在“纸上”变得更好。
您真的必须了解实例代码(非静态)和类代码(静态)之间的区别
当方法/属性不需要类的实例工作时,使用 static 代码(类方法/属性)。一个很好的例子是图像绘制方法:Image.draw()
实例方法/属性对于保持给定对象的状态非常有用,该对象必须与其他对象中的数据分开。
例如,如果您的游戏中有Player
个类,并且您有两个实例player1
和player2
,那么每个实例都有自己的分数:
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
或者可能属于对象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)
}
}
而不是:
class Game {
GameState state = GameState.getInitialState( this );
void run() {
while( state.alive ) {
do X Y Z
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...
}
}
同样,这取决于具体情况,可能存在您不需要实例的情况(再次,像Image的draw()实用工具方法)
最后。
实例方法允许您在类方法不使用时使用多态(至少在Java和其他静态类型语言中)。
因此,如果您的代码是实例代码,您可能会受益于使用运行时委派和多态。例如,State pattern如果您的所有代码都是静态的,则不能使用它,但您可以使用实例代码:
{{1}}
再一次,只有这在面向对象方面才有意义,就像对象具有需要数据的属性一样?如果不是,保持该部分代码静态可能是好的。
所有这些概念(封装,多态,抽象,继承等)都是OO技术的本质,涵盖在OOA/D中,而它们看起来像语法糖(大多数时候都是)你的体验会告诉您什么时候应该有类代码以及何时实例代码。
答案 1 :(得分:2)
static
作为语义工具存在于任何其他工具中。当某些东西是静态的时,意味着到该类试图建模的东西。在你的情况下,你有效地使用静态来创建全局存储,你显然遇到了一些问题,提示全局变量的一般规则是“坏”。
如果静态接口的行为以你不想要的方式强迫你处理设计问题和实现问题(比如你的applet支持),那么是的,这是一个问题你应该考虑重构一个更合适的设计。
你应该摆脱所有的静电吗?也许。如果在任何时候不可能存在多个特定对象的单个实例,并且您愿意处理同步对象的访问的成本,则可能某些东西可能保持静态。我在你的例子中没有看到任何东西,我会根据我对他们所做的事情的猜测而绝对保持静态。
(关于你对链式对象引用的明显厌恶,你可能想要研究Law of Demeter以及可以使代码更符合它的技术。我认为代码不应该严格遵守它,但通常应该尝试保持链式方法调用有点短。)
答案 2 :(得分:2)
在我看来,使用静态分享一些全局状态而不必将其注入客户端类会使代码更难以测试,重用,破解等,因为你耦合那些客户端类到他们正在使用的静态成员的类。
这是一个人为的例子:
如果您想出两种不同类型的游戏类,并希望对它们进行基准测试,那该怎么办呢。
或许你想扩展你的引擎来运行两种完全不同类型的游戏(可能是平台游戏和射击游戏,谁知道),你可以通过修改游戏并保留所有游戏来实现这一点。其他课程相同。 (我说它是人为的。)
当你所有其他类访问Game类的静态成员时,你必须编写2个版本的应用程序,每个版本都有自己的Game实现。
如果您只是将Game对象传递到需要它的每个类中,您可以将Game子类化并将任何类型的Game传递到需要它的客户端类中。
答案 3 :(得分:1)
过度使用静态通常会加剧对代码的更改并降低其可测试性。
考虑用普通对象替换静态对象。您可以将它们传递给对象的构造函数。这样,它们很容易替换为另一个实现(真实或模拟/存根进行测试)。这种技术称为dependency injection (DI)。
例如,您已将三个对象传递给您的应用程序:
并将其保存到字段中。这样就取代了
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)
}
}
差异看起来微妙,但实际上很重要,因为您的代码将变得更加模块化。
答案 4 :(得分:1)
我对静力学的个人经验实际上是双重的。首先,我发现静态是一种反模式,经常混淆和隐藏我的设计有问题的事实。如果我发现自己需要一个静态变量,我必须问问自己为什么。但是,有时需要静电,实际上是合适的。在这些情况下,我试图将真正全局的部分与程序隔离开来,并将它们放在自己的单例类中,而不是某处的静态变量。虽然它可能只是语义,但根据我的经验,将它们设为单身可以让事物在OO意义上更加清洁。