有关具有许多不同子类的类型切片的设计问题
我经常遇到的一个基本问题,但是找到一个干净的解决方案是,我们要在其中编写行为代码,以便在公共基类或者接口的不同对象之间进行交互。为了更具体一点,我举一个例子。
鲍勃一直在编写支持"酷地理效应"的策略游戏。这些因素汇总到简单的约束条件中,例如,如果部队在水中行走,则速度会降低25%。如果他们在草地上行走,则速度降低5%,如果在人行道上行走,则速度降低0%。
现在,管理层告诉鲍勃,他们需要新的部队。会有吉普车,小船和气垫船。另外,他们希望吉普车进入水中后受到损害,并且气垫船会忽略这三种地形。有传言说,他们可能会增加另一种地形类型,其功能要比放慢单位速度和造成伤害要多。
一个非常粗糙的伪代码示例如下:
public interface ITerrain { void AffectUnit(IUnit unit); } public class Water : ITerrain { public void AffectUnit(IUnit unit) { if (unit is HoverCraft) { // Don't affect it anyhow } if (unit is FootSoldier) { unit.SpeedMultiplier = 0.75f; } if (unit is Jeep) { unit.SpeedMultiplier = 0.70f; unit.Health -= 5.0f; } if (unit is Boat) { // Don't affect it anyhow } /* * List grows larger each day... */ } } public class Grass : ITerrain { public void AffectUnit(IUnit unit) { if (unit is HoverCraft) { // Don't affect it anyhow } if (unit is FootSoldier) { unit.SpeedMultiplier = 0.95f; } if (unit is Jeep) { unit.SpeedMultiplier = 0.85f; } if (unit is Boat) { unit.SpeedMultiplier = 0.0f; unit.Health = 0.0f; Boat boat = unit as Boat; boat.DamagePropeller(); // Perhaps throw in an explosion aswell? } /* * List grows larger each day... */ } }
如我们所见,如果Bob从一开始就拥有可靠的设计文档,情况将会更好。随着单位数量和地形类型的增加,代码的复杂性也随之增加。鲍勃不仅要担心弄清楚可能需要将哪些成员添加到单元界面,而且还必须重复很多代码。新的地形类型很可能需要从基本IUnit接口中获取的其他信息。
每次我们向游戏中添加另一个单位时,必须更新每个地形以处理新单位。显然,这会导致很多重复,更不用说丑陋的运行时检查了,它确定了要处理的单元的类型。在此示例中,我选择了对特定子类型的调用,但是必须进行此类调用。一个例子是,当船撞到陆地时,其螺旋桨应受到损坏。并非所有单位都有螺旋桨。
我不确定这种问题叫什么,但是这是多对多的依赖,我很难解耦。我不希望ITerrain上的每个IUnit子类都有100个重载,因为我想通过耦合来清理。
对此问题的任何追捧都备受追捧。也许我正在一起思考脱离轨道吗?
解决方案
回答
旧主意:
Make a class iTerrain and another class iUnit which accepts an argument which is the terrain type including a method for affecting each unit type example: boat = new iUnit("watercraft") field = new iTerrain("grass") field.effects(boat)
好吧,忘记所有我有更好的主意:
使每个地形的效果成为每个单元的属性
例子:
public class hovercraft : unit { #You make a base class for defaults and redefine as necessary speed_multiplier.water = 1 } public class boat : unit { speed_multiplier.land = 0 }
回答
地形具有地形属性
地形属性是多维的。
单位有一个推进力。
推进与地形属性兼容。
单位以地形为动力,以地形来访。
那被委托给推进。
参观过程中,单位可能会受到地形的影响。
单位代码对推进一无所知。
地形类型可以更改,而不能更改除"地形属性"和"推进"之外的任何内容。
Propuslion的建设者保护现有单位免受新的旅行方式的影响。
回答
我们在这里遇到的限制是,与某些其他OOP语言不同,C#缺少多次调度。
换句话说,给定这些基类:
public class Base { public virtual void Go() { Console.WriteLine("in Base"); } } public class Derived : Base { public virtual void Go() { Console.WriteLine("in Derived"); } }
该功能:
public void Test() { Base obj = new Derived(); obj.Go(); }
即使引用" obj"的类型为Base,也将正确输出" in Derived"。这是因为C在运行时会正确找到最派生的Go()进行调用。
但是,由于Cis是单一的调度语言,因此它仅对OOP语言中隐含的" this"的" first parameter"执行此操作。以下代码无法像上面那样工作:
public class TestClass { public void Go(Base b) { Console.WriteLine("Base arg"); } public void Go(Derived d) { Console.WriteLine("Derived arg"); } public void Test() { Base obj = new Derived(); Go(obj); } }
这将输出" Base arg",因为除" this"之外,所有其他参数都是静态调度的,这意味着它们在编译时已绑定到被调用的方法。在编译时,编译器唯一了解的是传递的参数的声明类型(" Base obj"),而不是其实际类型,因此方法调用绑定到Go(Base b)。
那么,解决问题的方法是,基本上是手动编写一个小的方法分派器:
public class Dispatcher { public void Dispatch(IUnit unit, ITerrain terrain) { Type unitType = unit.GetType(); Type terrainType = terrain.GetType(); // go through the list and find the action that corresponds to the // most-derived IUnit and ITerrain types that are in the ancestor // chain for unitType and terrainType. Action<IUnit, ITerrain> action = /* left as exercise for reader ;) */ action(unit, terrain); } // add functions to this public List<Action<IUnit, ITerrain>> Actions = new List<Action<IUnit, ITerrain>>(); }
我们可以使用反射来检查传入的每个动作的通用参数,然后选择与给定的单位和地形相匹配的最派生的参数,然后调用该函数。添加到动作的功能可以在任何地方,甚至可以分布在多个程序集中。
有趣的是,我已经遇到过几次这个问题,但是从来没有超出游戏的范围。
回答
将交互规则与Unit和Terrain类分离;互动规则比这更笼统。例如,可以使用哈希表,其中的键是一对交互类型,而值是对这些类型的对象进行操作的"有效"方法。
当两个对象必须交互时,在哈希表中找到所有交互规则并执行它们
这消除了类间的依赖,更不用说原始示例中的丑陋的switch语句了。
如果性能成为问题,并且交互规则在执行过程中没有更改,请在遇到类型对时对它们的规则集进行缓存,并发出新的MSIL方法以一次运行它们
回答
这里肯定有三个对象在玩:
1) Terrain 2) Terrain Effects 3) Units
我不建议创建以地形/单位对作为查找动作的关键的地图。随着单位和地形列表的增加,这将使我们难以确保已涵盖所有组合。
实际上,似乎每个地形单元组合都具有独特的地形效果,因此,我们是否会从完全具有相同地形效果的列表中看到好处,这是令人怀疑的。
取而代之的是,我会让每个单元维护自己的地形图到地形效果图。然后,地形可以仅调用Unit-> AffectUnit(myTerrainType),并且该单元可以查找地形将对自身产生的影响。