复合模式/实体系统和传统OOP

时间:2011-02-09 06:30:10

标签: java design-patterns language-agnostic composite

我正在开发一个用Java编写的小游戏(但这个问题与语言无关)。由于我想探索各种设计模式,我被挂在Composite pattern / Entity系统(我最初阅读herehere)上,作为典型深层次继承的替代方案。

现在,在编写了几千行代码后,我有点困惑。我认为理解模式,我喜欢使用它。我认为它非常酷,而且星巴克也是如此,但它觉得它提供的好处有点短暂而且(最让我烦恼的是)在很大程度上取决于你的粒度。

以下是上面第二篇文章的图片: enter image description here

我喜欢对象(游戏实体或任何你想要称之为的东西)的方式有一组最小的组件,推断的想法是你可以编写看起来像这样的代码:

BaseEntity Alien = new BaseEntity();
BaseEntity Player = new BaseEntity();

Alien.addComponent(new Position(), new Movement(), new Render(), new Script(), new Target());
Player.addComponent(new Position(), new Movement(), new Render(), new Script(), new Physics());

..这将是非常好的...但在现实中,代码最终看起来像

BaseEntity Alien = new BaseEntity();
BaseEntity Player = new BaseEntity();

Alien.addComponent(new Position(), new AlienAIMovement(), new RenderAlien(), new ScriptAlien(), new Target());
Player.addComponent(new Position(), new KeyboardInputMovement(), new RenderPlayer(), new ScriptPlayer(), new PhysicsPlayer());

似乎我最终得到了一些由较少组件组成的非常专业的组件。通常,我必须制作一些具有其他组件依赖性的组件。毕竟,如果你没有位置,你怎么渲染?不仅如此,你最终渲染玩家与外星人和手榴弹的方式可能根本不同。除非你制作一个非常大的组件(在这种情况下......为什么你还在使用复合模式?)

你不能拥有一个指示所有三个组件的组件。

给出另一个真实的例子。我的游戏中有角色可以装备各种装备。当装备一件装备时,会改变一些统计数据以及视觉上显示的内容。这是我的代码现在的样子:

billy.addControllers(new Movement(), new Position(), new CharacterAnimationRender(), new KeyboardCharacterInput());

billy.get(CharacterAnimationRender.class).setBody(BODY.NORMAL_BODY);
billy.get(CharacterAnimationRender.class).setFace(FACE.BLUSH_FACE);
billy.get(CharacterAnimationRender.class).setHair(HAIR.RED_HAIR);
billy.get(CharacterAnimationRender.class).setDress(DRESS.DRAGON_PLATE_ARMOR);

以上CharacterAnimationRender.class仅影响显示的内容。{所以我显然需要制作另一个处理齿轮属性的组件。但是,为什么我会这样做:

billy.addControllers(new CharacterStatistics());

billy.get(CharacterAnimationRender.class).setBody(BODY.NORMAL_BODY);
billy.get(CharacterStatistics.class).setBodyStats(BODY_STATS.NORMAL_BODY);

当我可以制作一个CharacterGearStuff控制器/组件来处理统计数据的分布以及视觉变化时?

总而言之,我不确定这应该如何帮助提高工作效率,因为除非您想手动处理所有内容,否则您仍然需要创建依赖于2个以上组件的“元组件”(并修改/交叉修改)他们所有的子组件 - 将我们带回OOP)。或者也许我在想它完全错了。我呢?

4 个答案:

答案 0 :(得分:6)

听起来你有点误解了组件模式。

组件只是数据,没有代码。如果你的组件中有代码,那么它不再是一个组件 - 它更复杂。

因此,例如,您应该能够轻松地分享您的CharacterAnimationRender和CharacterStatistics,例如:

CharacterStats { int BODY }
CharacterGameStats { ...not sure what data you have that affects gameplay, but NOT the rendering... }
CharacterVisualDetails { int FACE, int HAIR }

...但是不需要让这些人知道彼此的存在。当你谈论组件之间的“依赖关系”时,我暂停你已经迷路了。一个int的结构如何“依赖”另一个int结构?他们不能。它们只是大块的数据。

...

一开始就回到你的问题,你最终得到了:

Alien.addComponent(new Position(), new AlienAIMovement(), new RenderAlien(), new ScriptAlien(), new Target());
Player.addComponent(new Position(), new KeyboardInputMovement(), new RenderPlayer(), new ScriptPlayer(), new PhysicsPlayer());

......那很完美。假设您已正确编写这些组件,您已将数据拆分为易于读取/调试/编辑/编码的小块。

然而,这是在猜测,因为你没有指明这些组件中的内容......例如AlienAIMovement - 那是什么?通常情况下,我希望你有一个“AIMovement()”,然后编辑它以使其成为Alien的版本,例如更改该组件中的一些内部标志,以使用AI系统中的“Alien”功能来区分它。

答案 1 :(得分:2)

大卫,

首先感谢你提出的完美问题。

我了解您的问题,并认为您没有正确使用该模式。请阅读这篇文章:http://en.wikipedia.org/wiki/Composite_pattern

例如,如果你不能实现通用类运动并需要AlienAIMovement和KeyboardMovement,你可能应该使用访问者模式。但在您开始重构数千个代码行之前,请检查您是否可以执行以下操作。

是否有机会编写接受BaseEntity类型参数的类Movement?可能所有Movement的实现之间的差异只是一个参数,标志左右?在这种情况下,您的代码将如下所示:

Alien.addComponent(new Position(), new Movement(Alien), new Render(Alien), new Script(Alien), new Target());

我认为情况并非如此糟糕。

如果不可能,请尝试使用factory创建实例,所以

Alien.addComponent(f.createPosition(), f.createMovement(Alien), f.createRender(Alien), f.createRenderScript(Alien), f.createTarget());

我希望我的建议有所帮助。

答案 2 :(得分:2)

Ents似乎旨在完全按照您的意愿行事。如果你仍然想要自己的图书馆,你至少可以从它的设计中学习。

以前的答案看起来都很笨拙,并且会产生大量不必要的物品,IMO。

答案 3 :(得分:1)

我认为你在这里使用了错误的方法。应该采用模式来满足您的需求,而不是相反。简单总是比复杂更好,如果你觉得某些东西不能正常工作,这意味着你应该退一步,也许从一开始就开始。

对我来说,这已经有了代码味道:

BaseEntity Alien = new BaseEntity();
Alien.addComponent(new Position(), new AlienAIMovement(), new RenderAlien(), new ScriptAlien(), new Target());

我希望面向对象的代码看起来像这样:

Alien alien = new AlienBuilder()
    .withPosition(10, 248)
    .withTargetStrategy(TargetStrategy.CLOSEST)
    .build();

//somewhere in the main loop
Renderer renderer = getRenderer();
renderer.render(alien);

当您为所有实体使用泛型类时,您将拥有一个非常通用且难以使用的API来处理您的对象。

此外,在同一个组件伞下使用Position,Movement和Renderer这样的东西感觉不对。位置不是一个组件,它是一个属性。移动是一种行为,而渲染器根本就与您的域模型无关,它是图形子系统的一部分。组件可以是汽车,车身零件和外星人枪支的轮子。

游戏开发是一件非常复杂的事情,从第一次尝试开始就很难做到。从头开始重写代码,从错误中吸取教训并感受自己在做什么,而不仅仅是尝试从某篇文章中定制模式。如果你想在模式上做得更好,你应该尝试别的东西而不是游戏开发。例如,编写一个简单的文本编辑器。