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

时间:2009-03-25 23:55:18

标签: c# design-patterns software-design

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

我们称之为 DeathBlaster 4:Deathening 。在DB4中,您有许多Ship个对象,这些对象在旅行时会定期和随机地遇到Phenomena。给定的Phenomenon Effects可能会遇到Ship的{​​{1}}。例如,我们可能有四种Ships和三种Phenomena

                              Phenomena
              ==========================================
Ships         GravityWell     BlackHole      NebulaField
------------  ------------------------------------------
RedShip       +20% speed      -50% power     -50% shield
BlueShip      no effect       invulnerable   death              Effects of Various
GreenShip     -20% speed      death          +50% shield        Phenomena on Ships
YellowShip    death           +50% power     no effect    

此外,Effects可能会相互影响。例如,GreenShipGravityWell中的NebulaField可以在生成的SpeedEffectShieldEffect之间获得某种协同作用。在这种情况下,协同效应本身就是Effect - 例如,这种相互作用可能会产生PowerLevelSynergyEffect。除了Effects Ship集合Ship之外,不需要任何其他信息来解决最终结果应该是什么。

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

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

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

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



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

你可以看到,例如,尽管表中有12种交互组合,但只有5种效果:对速度的修改,对权力的修改,对盾牌的修改,不可破坏性,死亡。

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

EffectStack然后使用Effects通过在其现有属性和属性中添加修饰符来确定InteractionResolver的实际结果。

我喜欢这个,因为:

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

7 个答案:

答案 0 :(得分:1)

一个有趣的潜在选择是使用Visitor Pattern的变体。

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

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

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

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

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

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

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

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

答案 3 :(得分:0)

听起来像是经典的multiple dispatch problem

答案 4 :(得分:0)

有趣的问题

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

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

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

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

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

IShip myShip = myShip.AddPhenomena(PhenomenaGeneratedByAFactoryForThisShip);

在这种现象中,您将方法与原始船舶包装在一起,因此您可以对属性和所有属性进行修改。

此外,如果你使用strategy pattern,你可以产生任何你想要的现象。

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

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

答案 5 :(得分:0)

  

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

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

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

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

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

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

如果有M艘船,那么你只需要log2(M)属性就可以为每艘船提供独特的行为。

答案 6 :(得分:0)

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

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

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

这里的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');

如果更改了流程方法,只需修改Phenomena类中的流程方法。

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

或者你认为还有其他的东西不仅现象需要改变船舶的状态(像其他船只,浅滩岩石), 你需要在ship class中维护一个进程表,并将Phenomena作为其中之一,

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