面向对象的设计,交互对象

时间:2013-11-29 19:18:45

标签: c# oop design-patterns

这个问题让我想起了小游戏涂鸦神。有几个对象,其中一些可以相互交互并形成新对象。每个对象自然是它自己的类:水,火,空气等。这些都继承自同一个基类。例如,水和火物体可以组合形成灰烬物体,可以用于新的组合。

问题在于找出一种处理所有可能组合的优雅方式。最明显但又非常难以维护的解决方案是创建一个函数,它将任意两个对象作为参数,并使用一个巨大的开关块来比较类型名,并找出当这两个对象交互时应该返回什么类型的对象(如果有的话)。组合(a,b)应始终等于组合(b,a)也很重要。

这种情况的可维护和高效设计是什么?

4 个答案:

答案 0 :(得分:2)

我们不得不在游戏中为此拍摄代码来碰撞项目。我们最终选择了存储一系列委托方法的二维结构。

      | air            |  wind            | fire
air   |combine(air,air)|combine(air,wind) |combine(air,fire)
wind  |                |combine(wind,wind)|combine(wind,fire)
fire  |                |                  |combine(fire,fire)

通过一些思考,你只需要填充组合矩阵的一半以上。

你可以(例如):

lookup = 
     new Dictionary<
            Tuple<Type, Type>,
            Func<ICombinable, ICombinable, ICombinable>();
lookup.Add(
   Tuple.Create(typeof(Air), typeof(Fire)),
   (air,fire) => return new Explosion());

然后有一个方法:

ICombinable Combine(ICombinable a,ICombinable b)
{
    var typeA = a.GetType();
    var typeB = b.GetType();
    var typeCombo1 = Tuple.Create(typeA,typeB);
    Func<ICombinable,ICombinable,ICombinable> combineFunc;
    if(lookup.TryGetValue(typeCombo1, out combineFunc))
    {
        return combineFunc(a,b);
    }
    var typeCombo2 = Tuple.Create(typeB,typeA);
    if(lookup.TryGetValue(typeCombo2, out combineFunc))
    {
        return combineFunc(b,a);
    }
     //throw?
}

答案 1 :(得分:1)

所有游戏对象都已经以某种方式设计。它们要么是硬编码的,要么是在运行时从资源中读取的。

此数据结构可以轻松存储在Dictionary<Element, Dictionary<Element, Element>>

var fire = new FireElement();
var water = new WaterElement();
var steam = new SteamElement();

_allElements = Dictionary<Element, Dictionary<Element,Element>>
{
    new KeyValuePair<Element, Dictionary<Element, Element>>
    {
        Key = fire,
        Value = new KeyValuePair<Element, Element>
        {
            Key = water,
            Value = steam
        }
    },
    new KeyValuePair<Element, Dictionary<Element, Element>>
    {
        Key = water,
        Value = new KeyValuePair<Element, Element>
        {
            Key = fire,
            Value = steam
        }
    }

}

加载或定义元素时,您可以复制它们,因为最多只有几百个。由于易于编码IMO,开销可以忽略不计。

_allElements的键包含所有现有的可组合元素。 _allElements[SomeElement]的值会生成另一个字典,您可以在要与其组合的元素上访问该字典。

这意味着您可以使用以下代码找到组合的结果元素:

public Element Combine(Element element1, Element element2)
{
    return _allElements[element1][element2];
}

当这样称呼时:

var resultingElement = Combine(fire, water);

收益率steam,与Combine(water, fire)调用的结果相同。

未经测试,但我希望该原则适用。

答案 2 :(得分:0)

这正是接口的正确位置。使用它们,您可以避免大转换,并且每个元素类都可以实现自己与另一个elemt类交互的行为。

答案 3 :(得分:-1)

我建议使用Abstract Factory返回特定类型的界面,让我们说InteractionOutcome。你不会逃避使用switch-case的需要,但你最终会为每个“建筑”使用不同的工厂来维护更多的东西。

希望我帮忙!