Java: доступ к ресурсам и закон Деметры

обзор

в моей (Android) Java-игре у меня есть класс под названием ресурсы. Как следует из названия, этот класс имеет ресурсы для игры. Все мои объекты OpenGL (спрайты) создаются здесь

это выглядит примерно так (очевидно, это упрощенная версия по сравнению с той, которая появляется в реальном проекте):

public class Resources {

    Hero hero;
    Enemy enemy;
    MenuButtons mainMenuButtons;
    Background background;
    Scene mainMenu;

    public void createObjects(){

        hero = new Hero();
        enemy = new Enemy();
        mainMenuButtons = new MenuButtons();
        background = new Background();
        mainMenu = new Scene(this);

    }
}

Итак, в пределах моего mainMenu сцена, мне нужен доступ к моей объекты, поэтому мы можем увидеть что-то вроде этого:

public class mainMenu implements Scene {

    Resources resources;

    public mainMenu(Resources resources){

        this.resources = resources;

    }

    @Override
    public void render(){

        resources.background.draw();
        resources.hero.draw();
        resources.enemy.draw();
        mainMenuButtons.draw();           

    }

    @Override
    public void updateLogic(){

        resources.hero.move();
        resources.enemy.move();
        resources.mainMenubuttons.animate();
    }

}

теперь вышеуказанный метод - это всего лишь один из способов получить доступ к объектам в ресурсы и их методы. Но действительно ли это нарушает закон Деметры? если нет, то почему? Если да, то каков наилучший способ получить доступ к этим объектам таким образом, чтобы не нарушать LOD?

аксессоры?

один вариант (который я исключил TBH-см. ниже) помещает методы доступа в my ресурсы класса. Чтобы я мог сделать что-то вроде:

resources.drawBackround();

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

передача объектов в конструктор сцены

конечно, я тоже могу сделать что-то вроде этого:

    hero = new Hero();
    enemy = new Enemy();
    mainMenuButtons = new MenuButtons();
    background = new Background();
    mainMenu = new Scene(hero, enemy, mainMenuButtons, background);

поэтому я могу просто сделать это:

    background.draw(); //etc....

это выполнимо для простых сцен (например, систем меню, которые не требуют много объектов), но для основной игры это может быстро стать беспорядком, поскольку мне придется передавать ссылки на некоторые объекты 30+ в конструктор, который на самом деле не звучит совсем право......

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

7 ответов


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

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

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

Я предполагаю, что многие объекты имеют один и тот же класс. Таким образом, вам не нужно делать аксессуар для каждого объекта, что бы действительно взорвать класс. В игре у вас обычно есть герой, один или несколько врагов и много спрайтов.

public class Resources {

    private Hero hero;
    private Enemy enemy;
    private MenuButtons mainMenuButtons;
    private Background background;
    private Scene mainMenu;

    public void createObjects(){

        hero = new Hero();
        enemy = new Enemy();
        mainMenuButtons = new MenuButtons();
        background = new Background();
        mainMenu = new Scene(this);

    }

    public Hero getBackground() {

        return background;
    }

    public Hero getHero() {

        return hero;
    }

    public List<Enemy> getEnemies() {

        ArrayList<Enemy> list = new ArrayList<Enemy>();
        list.add(enemy);
        list.add(next_enemy);
        return list;
    }

    public List<Sprite> getSprites() {

        ArrayList<Sprite> list = new ArrayList<Sprite>();
        list.addAll(enemy.getActiveSprites());
        return list;
    }

}

вместо getHero () и getEnemy() вы также можете сделать getActor() метод, если Hero и Enemy являются производными от одного класса. Метод getSprites () - это просто пример того, как он может выглядеть.

если это решение не сработает для вас, у меня есть другое предложение.

заставьте класс ресурсов выполнить некоторую работу.

public class ResourceManager {

    private Hero hero;
    private Enemy enemy;
    private MenuButtons mainMenuButtons;
    private Background background;
    private Scene mainMenu;

    public void createObjects(){

        hero = new Hero();
        enemy = new Enemy();
        mainMenuButtons = new MenuButtons();
        background = new Background();
        mainMenu = new Scene(this);

    }

    public void render(Scene scene) {

        this.background.draw();
        if (scene != mainMenu) {

            this.hero.draw();
            this.enemy.draw();
        }
        this.mainMenuButtons.draw();           

    }

    public void updateLogic(Scene scene){

        this.hero.move();
        this.enemy.move();
        this.mainMenubuttons.animate();
    }

}

затем mainMenu вызывает логические методы непосредственно в классе RescourceManager.

public class mainMenu implements Scene {

    ResourceManager resourceManager;

    public mainMenu(ResourceManager resourceManager){

        this.resourceManager = resourceManager;
    }

    @Override
    public void render(){

        resourceManager.render(this);
    }

    @Override
    public void updateLogic(){

        resourceManager.updateLogic(this);
    }

}

Я надеюсь, что мои советы помогли вам немного разобраться, как продолжить проект.


вы можете использовать инъекцию зависимостей и исключить класс ресурсов. Тогда вы можете вызывать функции на своих полях и не нарушать закон Деметры.

вот пример использования инъекции конструктора:

public class MainMenu implements Scene {

   Background background;
   Hero hero;  
   Enemy enemy; 
   MenuButtons buttons

    public mainMenu(Background background, Hero hero,  Enemy enemy, MenuButtons buttons){

       this.background = background;
       this.hero = hero;
       this.enemy = enemy;
       this.buttons = buttons;   
    }

    @Override
    public void render(){

        this.background.draw();
        this.hero.draw();
        this.enemy.draw();
        this.mainMenuButtons.draw();           

    }

    @Override
    public void updateLogic(){

        this.hero.move();
        this.enemy.move();
        this.mainMenubuttons.animate();
    }

}

с инъекцией зависимостей вы передаете экземпляры в конструкторы и функции вместо "создания" их внутри вашего класса. Однако вам нужно где-то управлять своими экземплярами, и есть много библиотек, которые это сделают для тебя. Кинжал является популярным для Android:http://square.github.io/dagger/


идея передачи списка не является плохим первым шагом, но этого недостаточно. У разработчиков игр есть (несколько спорная) концепция структуры, называемой "графом сцены", которая помогает им отслеживать свои ресурсы (среди прочего). https://en.wikipedia.org/?title=Scene_graph

Это довольно сложная концепция, но рано или поздно вам придется узнать об этом. Есть много хороших советов gamedev.stackexchange.com поэтому я предлагаю вам посмотри туда.

вот хороший видеоурок YouTube по этому вопросу. https://www.youtube.com/watch?v=ktz9AlMSEoA


вы можете создать Drawer класс, который обрабатывает чертеж всех объектов. Ваши объекты сцены просто нужно кормить Drawer объекты, которые я предполагаю, являются Drawable.

public class Drawer {
   public void drawObjects(Drawable... objects) {
      for(Drawable drawable : objects) {
         drawable.draw();
      }
   }
}

это затем используется Scene рисовать эти объекты.

public class mainMenu implements Scene {
   Resources resources;
   Drawer drawer;

   ...

   public void render() {
      drawer.drawObjects(resources.background, 
                         resources.hero, 
                         resources.enemy, 
                         resources.mainMenuButtons);
   }

   ...
}

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

public interface Updateable {
   void update();
}

Heroи Enemy ' s update() методы могли бы просто назвать их move() методы, в то время как MenuButtons ' s update() может делегировать animate(), etc.

очевидно, если вам нравится, вы можете использовать какую-то коллекцию вместо varargs для параметра Drawer ' s drawObjects(). Я просто как хорошая беглость стало возможным благодаря С varargs, так как вам не придется создавать коллекции.

другие советы для поддержания кода в соответствии с законом Деметры, ознакомьтесь с этой статьей:закон Деметры и как с ним работать


Мне нравится концепция ResourceManager.
но ResourceManager должен быть responsilbe для загрузки ресурсов, кэширования и освобождения их.
рендеринг-это определенно метод объекта рендеринга.

Таким образом, метод Scence - render может делегировать ему рендеринг после
создания визуализации и кормить его рисунки в качестве рендера не
ресурсы render но объекты передать.

Скажи:

class MainMenu implements Scene {
    Renderer sceneRenderer = new Renderer();
    AnimatedRenderer animatedRenderer = new AnimatedRenderer();
    ResourceManager resourceManager = ResourceManager.getInstance();
    List<Resource> resources;
    List<Drawable> renderedObjects;
    GameObjectController gameObjectController;


    void initializeScene() {
          resources = resourceManager.getResources();
          renderedObjects = resourcesAsRenderables();
          sceneRenderer.setDrawables(renderedObjects);
    }

    List<Drawable> resourcesAsRenderables() {
      // if resources are not directly renderable, do decoration etc
      // and return a List of Drawable
    }

    @Override
    public void render(){
         sceneRenderer.render();
    }
    @Override
    public void updateLogic(){
       moveGameObjects();
       doAnimations();

    }
    protected void moveGameObjects() {
        gameObjectController.moveAllObjects(this, resources);
    }
    protected void doAnimations() {
        animatedRenderer.render(resources);
    }


    class ResourceManager {
       private static ResourceManager instance = null;
       List<Resource> resources;
       public ResourceManager getInstance() {
          if(instance == null) {
             instance = new ResourceManager();
             instance.loadResources();
          }
          return instance;
       }
       private void loadResources() { 
           resources = new LinkedList<Resource>();
           resources.add(new Hero());
           ....
       }
       public List<Resource> getResources() {
          return resources;
       }
    }

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


изменить это:

 public void render(){

        resources.background.draw();
        resources.hero.draw();
        resources.enemy.draw();
        mainMenuButtons.draw();           

    }
 @Override
    public void updateLogic(){

        resources.hero.move();
        resources.enemy.move();
        resources.mainMenubuttons.animate();
    }

С этого:

public void render(){
            resources.render();
    } 

@Override
    public void updateLogic(){
        resources.update();
} 

ResourceManager не обязательно знать, что находится внутри ресурсов. Это может быть один враг или десять, он не заботится о ResourceManager.

и так в "ресурсе" вы можете сделать:

  public void update(){
        hero.update();// Cause hero may, or may not move, he makes the choice
        enemy.update();//...
        mainMenubuttons.update();//.
    }
  public void render(){
  ...
  }

больше, чем это! вы можете изменить реализацию "ресурс" с интерфейсом, и вы будете программировать для интерфейсов, а не для реализаций, что круто! Таким образом, вы можете иметь ресурсы для в игре и еще один для меню, которые будут использоваться таким же образом: только изменение, во время выполнения, конкретные ресурсы вы будете в меню или в игре!

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


Как видно, ваши ресурсы не нужно воссоздавать, вместо этого они используют некоторые ресурсы, которые не могут быть перезагружены (возможно, изображения).

вы должны совместно использовать объект images В классе ресурсов и создавать свои объекты в классе сцены, в конструкторе сущностей вы можете получить предварительно загруженный общий ресурс.