什么是解决相互作用的组合爆炸的最佳方法?

我现在正在做的事情之一与游戏有一些相似之处。 为了说明的目的,我将使用一个虚构的,假设的游戏中的一个例子来解释我的问题。

我们称之为DeathBlaster 4:The Deathening 。 在DB4中,您有许多Ship对象,它们在旅行时会定期和随机地遇到Phenomena 。 给定Phenomenon可能对遇到它的Ship具有零个,一个或多个Effects 。 例如,我们可能有四种Ships和三种Phenomena

                              现象
               ==========================================
船舶GravityWell BlackHole NebulaField
 ------------ -------------------------------------- ----
 RedShip + 20%速度-50%功率-50%屏蔽
 BlueShip没有影响无懈可击的死亡效果
 GreenShip -20%速度死亡+ 50%盾牌现象
 YellowShip死亡+ 50%的力量没有效果    

此外, Effects可能会相互Effects 。 例如, GravityWellNebulaField可以在生成的SpeedEffectShieldEffect之间获得某种协同作用。 在这种情况下,协同效应本身就是一种Effect – 例如,可能存在由此交互产生的PowerLevelSynergyEffect 。 除了作用于ShipEffects集之外,不需要任何其他信息来解决最终结果应该是什么。

你可能会开始看到这里出现的问题。 作为一种天真的第一种方法, 要么每Ship都必须知道如何处理每一个Phenomenon ,或者每个Phenomenon都必须知道每Ship 。 这显然是不可接受的,所以我们希望将这些责任转移到其他地方。 显然,这里至少有一个外部课程,可能是某种Mediator或某种Visitor

但是最好的方法是什么? 理想的解决方案可能具有以下特性:

  • 就像添加新Phenomenon一样容易添加新Ship
  • 不产生影响的交互是默认的,不需要额外的代码来表示。 约定优于配置。
  • 了解Effects如何相互影响,并能够管理这些交互以决定最终结果。

我想,我已经决定了我的方法,但我很想知道最佳设计共识是什么。 你会从哪里开始的? 你会探索什么途径?



后续更新:感谢大家的回复。 这就是我最后做的事情。 我的主要观察结果是,相对于可能的Phenomena × Ships相互作用的数量,不同Effects的数量似乎很小。 也就是说,尽管存在许多可能的交互组合 ,但这些交互的结果种类数量较少。

例如,你可以看到,虽然表中有12种交互组合,但只有五种效果:速度修改,力量修改,盾牌修改,无敌性,死亡。

我引入了第三个类,即InteractionResolver ,以确定交互的结果。 它包含一个将Ship-Phenomenon对映射到Effects的字典(它们基本上是执行效果的委托和一些元数据)。 当计算交互的结果完成时,每个Ship都会传递一个EffectStack ,该EffectStack对应于它正在体验的效果。

然后, Ships使用EffectStack通过向其现有属性和属性添加修饰符来确定Effects对它们的实际结果。

我喜欢这个,因为:

  1. 船舶永远不需要了解现象。
    • Ship-Phenomena关系的复杂性被抽象到InteractionResolver中。
    • InteractionResolver抽象出如何解决多个可能复杂效果的细节。 船只必须根据需要应用效果。
    • 这可以实现其他有用的重构。 例如,可以通过制作EffectProcessorStrategy来区分船舶处理效果的EffectProcessorStrategy 。 默认可能是处理所有效果,但是, BossShip可能会通过使用不同的EffectProcessorStrategy忽略次要效果。

一个有趣的潜在选择是使用访客模式的变体。

Judith Bishop和R. Nigel Horspool撰写了一篇关于设计模式效率的论文,其中他们使用C#3特性解释了经典访客模式的各种变体。

特别是,我将看看它们如何与代表一起处理访问者模式。 使用列表或代表堆栈可以强有力地为您提供一种有趣的方法来处理来自多个对象的多个效果,并且更容易扩展类层次结构的任何一侧(添加附加或添加效果)而不会发生巨大的代码更改。

不是一个答案,但对我来说,这相当尖叫“属性模式”。 有一个众所周知的yegge对此咆哮,我认为它会为你提供一些不错的指针。

http://steve-yegge.blogspot.com/2008/10/universal-design-pattern.html

这看起来像经典的OOP多态性/访问者困境。 但是,您的要求使其更容易。

基本上,我会创建一个所有具体船舶派生的基础Ship 。 这个类有方法:

 class Ship { void encounterBlackHole() {} void encounterNebula() {} ... etc. ... }; 

使用空的默认实体。 添加新现象时,只需添加空体的新方法即可。 (方法可能有参数,如黑洞的坐标或重量等)

对于效果及其互动 – 我认为你应该添加更多关于你想要它的信息,例如。 这些相互作用是罕见的还是常见的,它们是否在某种程度上累积,它们是否局限于它们控制的一组有限的变量……

对我来说听起来像是经典的多重调度问题 。

有趣的问题

在某种程度上,船舶必须知道哪些现象可以影响它,以及影响它的现象对哪艘船有影响。

这可以存储在运行时解析的xml文件中。

也许你可以使用Decorator模式来计算效果。 您在运行时生成各种现象。

假设您的船只实现了接口IShip,并且代码中的任何地方都使用了IShip。

现在,假设您的所有现象也实现了接口IShip(装饰器设计模式所需)。

 IShip myShip = myShip.AddPhenomena(PhenomenaGeneratedByAFactoryForThisShip); 

在这种现象中,您将方法从原始包装中包装,因此您可以对属性和所有属性执行修改。

此外,如果您使用策略模式,您可以生成任何您想要的现象。

消除现象,可以通过走一堆装饰好的船只来使用,并重新包装,所以我认为没有问题。

至于协同作用,我想我会使用一种略微修改过的现象,只有当目标船具有所有现象时才会起作用。

要么每艘船都必须知道如何处理每一个现象,要么每个现象都必须知道每艘船。

我认为这是你问题的关键,如果每次船舶现象互动都是独一无二的,那就是这样。 在您创建的表中,似乎是这种情况,因此对于n个船舶和m现象,您需要指定n * m交互。 你闻到维护问题是对的。

唯一的出路是通过使现象的效果取决于船舶的性质,使船舶现象相互作用不是唯一的。 例如,我们可以说只有使用奇异氧化铝制造的船只才能在黑洞中幸存下来。

但是只有一处房产并没有给你买任何东西,你可以很容易地指出哪些船只受到黑洞的影响。 关键是多个属性表现出相同的组合爆炸。

因此,使用奇异氧化铝建造的船只将在黑洞中存活,而没有建造的船只可以更快地生存。 1属性允许您指定2个内容(1位= 2种可能性)。 用corbite发动机制造的船在经纱区域会更快。 具有柯氏引擎和奇异氧化铝的船舶将在星云场等处获得50%的盾牌。

通过向船舶添加属性,您可以避免指定每个现象如何与每艘船相互作用,但仍然存在每种现象,并且每艘船都表现出适当的行为。

如果有M个船只,那么您只需要log2(M)属性来为每艘船提供独特的行为。

我认为问题的答案取决于问题有多好。

我认为设计的方式取决于问题是什么(或问题将来会发生)

你给一个表,然后我认为解决方案是维护一个表并查询它。

这里的python代码:(未经过测试,仅显示示例)

 class Ship(): def __init__(self,type): self.type=type def encounterPhenomena(self,type): # let Phenomena to process ship p = Phenomena(type) p.process(self) class Phenomena(): processTable = {} def __init__(self,type): self.type=type def process(self,ship): try: self.processTable[self.type](ship.type) #query the internal table to process ship except: pass #if Phenomena don't know this ship type then no process.. def addType(type,processMethod): processTable[type]=processMethod #add new Phenomena, and add processMethod def run(): A = Ship(type = 'RedShip') A.encounterPhenomena(type='GravityWell'); 

如果更改了process方法,只需修改Phenomena类中的process方法即可。

如果您认为船舶需要知道如何处理现象,那么将流程方法更改为船舶类。

或者你认为还有其他的东西,不仅现象需要改变船舶的状态(像其他船只,浅滩岩石),你需要在船级保持一个过程表,并使现象成为其中之一,

再说一遍,如何设计取决于自己的问题。