Java:访问资源和Demeter法则

概观

在我的(Android)Java游戏中,我有一个名为resources的类。 顾名思义,这个职业拥有游戏的资源。 我所有的OpenGL对象(Sprites)都是在这里创build的

它看起来像下面这样(显然,与真实项目中显示的相比,这是一个简化的版本):

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 – 见下文)将accessor方法放入我的资源类中。 所以我可以做这样的事情:

 resources.drawBackround(); 

我有很多的对象,我需要每个对象的每个方法/variables的访问器。 不是很实际,似乎我正在编写大量额外的代码,而且最重要的是,它使资源类可笑地变得很长,因为它充满了这些访问器。 所以我不会走这条路

传入对象到场景的构造函数中

当然,我也可以这样做:

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

所以我可以简单地做到这一点:

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

这对于简单的场景(比如不需要很多对象的菜单系统)是可行的,但是对于主游戏来说,它很快就会变成一团糟,因为我必须将引用30个对象的引用传递给构造器听起来不太对劲……

所以,如果有人能指出最好的方法,为什么,我会很感激。

Solutions Collecting From Web of "Java:访问资源和Demeter法则"

所以,如果有人能指出最好的方法,为什么,我会很感激。

在我看来,最好的办法是保持资源类,使所有的对象都是私有的,不要违反法律和写入访问者(但不是像你已经排除的每个对象)。

我有很多的对象,我需要每个对象的每个方法/variables的访问器。 不是很实际,似乎我正在编写大量额外的代码,而且最重要的是,它使资源类可笑地变得很长,因为它充满了这些访问器。 所以我不会走这条路

我假设许多对象是同一类的。 所以你不必为每个对象做一个访问器,真的会炸掉这个类。 我是一个游戏,你通常有一个英雄,一个或多个敌人和许多精灵。

 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()方法。 getSprites()方法只是一个例子。

如果这个解决scheme不适合你,我还有另一个build议。

使资源类做一些工作。

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

我希望我的build议能帮助你弄清楚如何继续你的项目。

你可以使用dependency injection,并消除你的资源类。 那么你可以调用你的领域的function,不会违反德米特法。

这是一个使用构造函数注入的例子:

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

通过dependency injection,可以将实例传递给构造函数和函数,而不是在类中“新build”它们。 你需要在某个地方pipe理你的实例,并且有很多的库可以为你做。 Dagger是Android上stream行的一款: http : //square.github.io/dagger/

通过列表的想法并不是一个糟糕的第一步,但这还不够。 游戏开发者有一个叫做“场景图”的结构(有点争议),可以帮助他们跟踪他们的资源(等等)。 https://en.wikipedia.org/?title=Scene_graph

这是一个非常复杂的概念,但迟早你需要了解它。 gamedev.stackexchange.com上有很多很好的build议,所以我build议你先看一看。

这里有一个很好的YouTubevideo教程。 https://www.youtube.com/watch?v=ktz9AlMSEoA

您可以创build一个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接口inheritance,您都可以做同样的事情。

 public interface Updateable { void update(); } 

HeroEnemyupdate()方法可以简单的调用他们的move()方法,而MenuButtonsupdate()可以委托给animate()等等。

显然,如果你喜欢,你可以使用某种集合而不是可变参数的DrawerdrawObjects()的参数。 我只是喜欢由可变参数所带来的stream畅度,因为您不必创build集合。

有关保持代码符合德米特法律的其他技巧,请查看这篇文章: 德米特法则和如何使用

我喜欢ResourceManager的概念。
但ResourceManager应负责加载资源,caching和释放资源。
渲染是一个渲染对象的方法。

所以Scence-render方法可以把渲染委托给它
实例化渲染器,并使用Drawable作为渲染器不提供
渲染资源,但可渲染的对象。

说:

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

这明确地分离了在现场生命周期中执行任务的逻辑和责任。 一个负责检索资源的资源pipe理器,因为它们可能需要很长的加载时间,所以在内存不足的情况下caching或释放事件,从而隐藏客户端的细节。 负责显示对象的渲染器和负责移动对象的控制器。 控制器本身可以为键盘事件实现处理程序,但这不是必须对场景透明的。 渲染器可以交换背景或缩小或设置光照效果,但场景只调用渲染方法。 animation渲染器负责启动,渲染和停止animation。

改变这个:

  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(){ ... } 

更有什者! 你可以用一个接口来改变“资源”的实现,你将会为接口编程而不是为了实现,这很酷! 通过这种方式,您可以在游戏中拥有一个“资源”,另一个用于相同方式的菜单:仅在运行时更改菜单或游戏中的具体资源!

无论如何,并不总是需要填补得墨忒耳。

可以看到你的资源不需要重新创build,而是使用一些不能重新加载的资源(可能是图片)。

您应该在Resource类中共享图像对象,并在Scene类的构造函数中创build对象,以获取预加载的共享资源。