创建游戏库存系统,无需转换为派生

时间:2017-08-04 16:53:53

标签: c# unity3d design-patterns

我正在尝试实施一个高性能的游戏库存系统。我有这个抽象基类来存储库存中不同类型的项目,例如,硬币,手电筒,刀等。

public abstract class ObtainableItem 
{
    public string Name { get; private set; }

    public ObtainableItem(string name)
    {
        Name = name;
    }
}

例如,我有一个打开门的DoorKey。 DoorKey有一个属性KeyCode,用于打开一扇门。

public class DoorKey : ObtainableItem
{
    public int KeyCode { get; private set; }

    public DoorKey() : base("key")
    {
        KeyCode = 1234;
    }
}

所有ObtainableItem都存储在Inventory

public class Inventory
{
    const int slotCount = 2;
    ObtainableItem[] slots = new ObtainableItem[slotCount];

    public Inventory()
    {
        slots[0] = new DoorKey();
    }
}

现在假设用户从门上的库存中拖动DoorKey并触发Open方法

public class Door
{
    public void Open(ObtainableItem key)
    {
        if (key is DoorKey)
        {
            DoorKey doorKey = (DoorKey)key;
            if (doorKey.KeyCode == 1234)
            {
                // Open door
            }
        }
        else
        {
            // "can't use this item on a door"
        }
    }
}

如何避免从ObtainableItem转换为DoorKey?我已经读过,使用强制转换是不好的做法,它指的是糟糕的代码oop设计。理想情况下,Door类应该如下所示。我的库存系统有什么样的模式吗?

public class Door
{
    public void Open(DoorKey key)
    {
        if (key.KeyCode == 1234)
        {
            // Open door
        }
    }
}

1 个答案:

答案 0 :(得分:1)

为了易于实现和可读性,总会有例外情况。您所描述的内容很常见,如果不是典型的话。

另一种方法是在调用Door.Open的类中使用“控制”逻辑。这可以通过反思轻松实现:

public abstract class ObtainableItem
{
    public string Name { get; private set; }

    public ObtainableItem(string name)
    {
        Name = name;
    }
}

public abstract class WorldItem
{
}

public interface IActsOn<in TWorldItem>
    where TWorldItem : WorldItem
{
    void ApplyTo(TWorldItem worldItem);
}

public class World
{
    // If profiling shows that this is a performance issue, a cache keyed by tWorldItem, tInvItem 
    // should fix it.  No expiry or invalidation should be needed.
    private Action<ObtainableItem, WorldItem> GetApplyTo(Type tWorldItem, Type tInvItem)
    {
        var tActOn = typeof(IActsOn<>).MakeGenericType(tWorldItem);
        if (!tActOn.IsAssignableFrom(tInvItem))
        {
            return null;
        }
        var methodInfo = tActOn.GetMethod(nameof(IActsOn<WorldItem>.ApplyTo));

        return new Action<ObtainableItem, WorldItem>((invItem, worldItem) =>
        {
            methodInfo.Invoke(invItem, new object[] { worldItem });
        });
    }

    public bool IsDropTarget(WorldItem worldItem, ObtainableItem item) 
        => GetApplyTo(worldItem.GetType(), item.GetType()) != null;

    public void ActOn(WorldItem worldItem, ObtainableItem item)
    {
        var actOn = GetApplyTo(worldItem.GetType(), item.GetType());
        if (actOn == null)
        {
            throw new InvalidOperationException();
        }

        actOn(item, worldItem);
    }
}

虽然这会使World的实现稍微复杂化,但它简化了各种对象的实现:

class Door : WorldItem
{
    public void Unlock(string bitting)
    {
        if (bitting == "1234")
        {
            Console.WriteLine("Door Opened");
        }
        else
        {
            Console.WriteLine("Door could not unlock");
        }
    }
}

class DoorKey : ObtainableItem, IActsOn<Door>
{
    private readonly string Bitting;

    public DoorKey(string bitting)
        : base("Key")
    {
        this.Bitting = bitting;
    }

    public void ApplyTo(Door worldItem)
    {
        worldItem.Unlock(this.Bitting);
    }
}

class RubberChicken : ObtainableItem
{
    public RubberChicken()
        : base("Rubber chicken")
    {
    }
}

使用示例:

class Program
{
    static void Main(string[] args)
    {
        var key1 = new DoorKey("1234");
        var key2 = new DoorKey("4321");
        var rubberChicken = new RubberChicken();

        var door = new Door();

        var world = new World();
        Debug.Assert(!world.IsDropTarget(door, rubberChicken));
        Debug.Assert(world.IsDropTarget(door, key1));

        world.ActOn(door, key2);
        world.ActOn(door, key1);

        Console.ReadLine();
    }
}