Чрезмерное использование статики в моем проекте Java Game? - PullRequest
4 голосов
/ 13 октября 2009

В настоящее время я занимаюсь разработкой небольшой платформы на 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(...);
     }
   }

EDIT
Основываясь на ответах, я решил создать класс 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 теперь выглядит следующим образом, все снова работает нормально, и я могу переопределить поддержку этого апплета:)

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;
    }
    }

Ответы [ 5 ]

9 голосов
/ 13 октября 2009

Прежде всего.

Вам не нужно избавляться от всего своего статического кода только потому, что это сделает его лучше на «бумаге».

Вы действительно должны понимать, в чем разница между кодом экземпляра (не статичным) и кодом класса (статическим)

статический код (методы / атрибуты класса)используется, когда методы / атрибуты не нуждаются в экземпляре класса для работы.Очень хорошим примером является метод рисования изображения: 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 должен получить доступ к N-му элементу другого a, вполне вероятно, что указанный атрибут принадлежит объекту 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)
    }
}

Опять же, это зависит от ситуации, могут быть сценарии, когда вам не нужен экземпляр (опять же, как утилита draw ()метод изображения)

Наконец.

Методы экземпляра позволяют вам использовать полиморфизм, а методы класса - нет (по крайней мере, в Java и других статически типизированных языках).

Таким образом, вы можете воспользоваться преимуществами делегирования во время выполнения и полиморфизма, если вашкод является кодом экземпляра.Возьмите, например, шаблон State , вы не можете использовать его, если весь ваш код статичен, но вы можете использовать код экземпляра:

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

И снова, только если это имеет смыслс точки зрения ориентации объекта, как у объекта есть атрибуты, данные которых требуются?Если нет, то может быть полезно сохранить этот раздел кода статичным.

Все эти понятия (инкапсуляция, полиморфизм, абстракция, наследование и т. Д.) Являются самой природой технологии ОО и охватываются ООА / D , хотя они могут показаться синтаксическим сахаром (и большей частьювремена, когда они есть) ваш опыт скажет вам, когда у вас должно быть что-то вроде class code и когда как instance code.

2 голосов
/ 13 октября 2009

На мой взгляд, использование статики для совместного использования некоторого глобального состояния вместо необходимости вставлять его в клиентские классы затрудняет тестирование, повторное использование, взлом и т. Д., Поскольку вы связываете эти клиентские классы с класс, чьи статические члены они используют.

Вот надуманный пример:

Что если, скажем, вы придумали 2 различных типа класса Game и хотите сравнить их оба.

Или, может быть, вы хотите расширить свой движок для запуска двух совершенно разных типов игр (может быть, платформер и стрелялка, кто знает), и так получилось, что вы можете сделать это, изменив Game и сохранив все другие классы такие же. (Я сказал, что это было придумано.)

Поскольку все ваши другие классы имеют доступ к статическим членам класса Game, вы должны написать 2 версии вашего приложения, каждое со своей собственной реализацией Game.

Если вы просто передали объект Game в каждый класс, который в нем нуждается, вы можете создать подкласс Game и передать любой тип Game в клиентские классы, которые в этом нуждаются.

2 голосов
/ 13 октября 2009

static существует как семантический инструмент, как и любой другой. Когда что-то статично, это имеет значение , что означает тому, что класс пытается смоделировать. В вашем случае вы эффективно используете static для создания глобального хранилища, и вы, очевидно, столкнулись с некоторыми проблемами, когда общее правило глобальных правил «плохое».

Если поведение ваших статических интерфейсов вынуждает вас обращаться с проблемами проектирования и реализации так, как вы этого не хотите (например, с поддержкой вашего апплета), тогда да, это проблема, и вы должны рассмотреть возможность рефакторинга в более подходящий дизайн.

Должны ли вы избавиться от всей статики? Может быть. Если невозможно одновременное существование более чем одного экземпляра определенного объекта, и вы готовы оплатить расходы на синхронизацию доступа к объекту, то, возможно, что-то может оставаться статичным. Я не вижу в вашем примере ничего такого, что могло бы сохранять статичность, основываясь на моем предположении о том, что они делают.

(Что касается вашего кажущегося отвращения к ссылкам на связанные объекты, вы можете рассмотреть Закон Деметры и методы, которые могут привести код в соответствие с ним. Я не думаю, что код должен придерживаться строго, но, как правило, должна быть попытка сделать вызовы метода несколько короткими.)

1 голос
/ 13 октября 2009

Мой личный опыт со статиками действительно двойственный. Во-первых, я обнаружил, что статика - это анти-шаблон, который часто запутывает и скрывает тот факт, что у меня есть проблемы с моим дизайном. Если мне понадобится статическая переменная, я должен спросить себя, почему. Тем не менее, бывают ситуации, когда статика необходима, а на самом деле уместна. В этих случаях я пытаюсь выделить фрагменты, которые являются действительно глобальными для программы, и поместить их в свой собственный одноэлементный класс, а не где-то статическую переменную. Хотя это может быть просто семантикой, из-за того, что я делаю их одноэлементными, в моем понимании все становится немного чище.

1 голос
/ 13 октября 2009

Чрезмерное использование статики обычно затрудняет внесение изменений в код и снижает его тестируемость.

Рассмотрите возможность замены статических объектов обычными объектами. Вы можете передать их в конструктор вашего объекта. Таким образом, они могут быть легко заменены другой реализацией (реальной или фиктивной / заглушкой для тестирования). Этот метод называется внедрение зависимостей (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.
  • Один взгляд на ваш код показывает, какие объекты используются, не нужно искать вызовы статических методов.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...