在我的 Java 游戏项目中过度使用静态?

Posted

技术标签:

【中文标题】在我的 Java 游戏项目中过度使用静态?【英文标题】:Overuse of static in my Java Game project? 【发布时间】:2009-10-13 16:15:35 【问题描述】:

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

一方面,它非常方便,因为我不必在每个类(如地图或玩家)中保留对游戏实例的引用。另一方面......我已经不得不去掉小程序支持,因为那里有很多静态的东西。

所以我的问题是,既然您可能比我更有经验的 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 好的,我已经在使用方法调用时遇到了问题,编译器给了我错误,因为它在原始版本中找不到我的子类 Game 的方法,我想我将在这里使用普通字段。

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

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;
    
    

【问题讨论】:

在 Java 中最好有:public Game getGame()public Game game() 【参考方案1】:

首先。

您不必仅仅因为这会使它在“纸面”上变得更好而摆脱所有的静态代码。

你真的要明白实例代码(非静态)和类代码(静态)有什么区别

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

instance 方法/属性有助于保持给定对象的状态,必须将其与其他对象中的数据分开。

例如,如果您的游戏中有 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,或者可能该对象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 和其他静态类型语言中)。

因此,如果您的代码是实例代码,您可能会受益于使用运行时委托和多态性。以State pattern 为例,如果您的所有代码都是静态的,您将无法使用它,但您可以使用实例代码:

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... 
     
 

再一次,只有当这在面向对象方面有意义时,对象是否具有需要其数据的属性?如果不是,最好保持那段代码静态。

所有这些概念(封装、多态、抽象、继承等)都是 OO 技术的本质,并在 OOA/D 中进行了介绍,尽管它们可能看起来像是语法糖(而且大多数情况下它们都是),但您的经验会告诉我们你什么时候应该有 class 代码,什么时候应该有 instance 代码。

【讨论】:

Oscar:进行了我不确定的编辑 - 如果它反映了您的意思,您可能需要查看:“实例方法/属性对于保持给定对象的状态和 [- - 不要将其与数据混合--] 必须与其他对象中的数据分开。” 我写得再好不过了(实际上我没有)。好久没见你了,你最近在干嘛?重构的小伙伴怎么样了? 我已经根据您的回答编辑了我的问题,这是一种方法吗?我的主要问题是让它易于阅读,但另一方面又要实用。 是的,虽然它可能会将您的链:this.game.animation.other.y.z 更改为this.game().animation().other().y().z() 的链,并且您必须输入更多。将功能封装在单个方法中将允许您键入:this.game.doZ() 和内部 doZ() 您可以链接您的属性。关键是在没有要保留的 state 时使用 static (就像在辅助方法中一样),并在您使用 non-static 时使用做。 想想人体,你的肺、血液和心脏都没有暴露给别人为你呼吸(我的意思是在正常情况下)你会有一个呼吸方法(公众界面),这将使您在内部将空气输送到肺部,而肺部又将氧气放入血液中,而心脏将氧气分配到肺部。你看不到body.lungs.blood.heart 链,但body.breath() ;)【参考方案2】:

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

如果您的静态接口的行为迫使您以您不希望的方式处理设计问题和实现问题(例如您的小程序支持),那么是的,这是一个问题,您应该考虑重构为更合适的设计。

你应该摆脱所有的静电吗?也许。如果某个特定对象的多个实例不可能同时存在,并且您愿意承担同步访问该对象的成本,那么也许某些东西可以保持静态。不过,根据我对他们所做的事情的猜测,我在您的示例中看不到任何我绝对会保持静态的内容。

(关于您对链式对象引用的明显厌恶,您可能需要研究Law of Demeter 以及可以使代码更符合它的技术。我认为代码不应该严格遵守它,但是有通常应该尝试使链接的方法调用稍微短一些。)

【讨论】:

【参考方案3】:

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

这是一个人为的例子:

假设你想出了 2 种不同类型的 Game 类,并且想要对它们进行基准测试。

或者,也许您想扩展您的引擎以运行两种完全不同类型的游戏(可能是平台游戏和射击游戏,谁知道呢),碰巧您可以通过修改 Game 并保留所有其他类同理。 (我说这是人为的。)

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

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

【讨论】:

我同意您给出的示例,但是,静态访问修饰符具有用途。例如,当完全要保持 NO 状态时,创建实例来执行实用方法 (like Integer.parseInt()) 是没有意义的,关键是要了解差异。跨度> 当然有它的用途。不过,我只是在谈论使用它来共享状态。【参考方案4】:

过度使用静态变量通常会使代码更改变得困难并降低其可测试性。

考虑用普通对象替换静态对象。您可以将它们传递给对象的构造函数。这样,它们可以很容易地替换为另一个实现(用于测试的真实或模拟/存根)。这种技术称为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 类中进行更改。 一目了然您的代码会显示使用了哪些对象,无需查找静态方法调用。

【讨论】:

暴露对象的内部也会产生耦合,最好将它们封装和隐藏。 是的,如果我们谈论的是内部结构。在我看来,声音、动画等并不是应用程序的内部内容,它们是必不可少的东西。但是,当然,您不必一个接一个地传递它们,您可以将它们分组到一个 .ctor 参数中(例如您的 Game 类)。请记住,如果它变得又大又多,请再次开始重构(但无论如何都应该记住这一点=))【参考方案5】:

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

【讨论】:

以上是关于在我的 Java 游戏项目中过度使用静态?的主要内容,如果未能解决你的问题,请参考以下文章

在 IOS 中保存和加载 XML 文件会在我的 Unity 项目中产生问题

如何在我的Flutter应用中集成游戏?

Java 实现2048游戏之详细教程

[JavaSE] 练手小项目 贪吃蛇小游戏

我想在我的 Android 游戏中播放音乐以及分析声音数据。使用 Unity C#

java小项目之:坦克大战,90后的集体回忆杀!