【问题标题】:Using GetType/instanceof in C# vs. alternatives在 C# 中使用 GetType/instanceof 与替代方案
【发布时间】:2015-07-06 08:30:21
【问题描述】:

我在使用 C# 制作的游戏中遇到了问题。这是一个简单的基于图块的匹配游戏,而我正在尝试制作的电源出现问题:

假设我们有基本的瓷砖类型,圆形、正方形和菱形,它们都是 Tile 的子类。我尝试将“匹配”行为提取到抽象的 Tile 方法:canMatchWith(Tile t),而不是让圆圈只匹配圆圈。 Tiles 也有两种方法来添加/删除它们可以匹配的 Tiles。

假设我们在游戏中间有一个圆形图块,并且我们有一个通电,上面写着“圆形图块可以在本回合与正方形图块匹配”。我会遍历所有圆形图块并说 circleTile.addCanMatchWith(typeof(Square))。在内部,我们有一个 List canMatchWith。

然后,我想说“圆不能再与正方形匹配”,简单地说 circleTile.removeCanMatchWith(typeOf(Square))。

这是我目前的解决方案,它运行良好,没有我注意到的性能缺陷(这是一个基于图块的匹配游戏,因此这些类型每次“移动”只评估一次,而不是逐帧评估)。但是,我脑海中的声音告诉我,这是完成此行为的不好方法。所以我有一些选择:

  1. 枚举... 每个 Tile 可以由 Tiletype 类型变量组成。这将在构造函数中初始化并设置为 Type.SQUARE 用于正方形,依此类推。然后,每个 Tile 都会有一个 List canMatchWith,功能和我原来的实现是一样的。除了在这种情况下,它有点棘手。假设我有一些圆形子类,椭圆形和椭圆形。我希望椭圆只能匹配正方形,但椭圆可以匹配所有圆圈而不是正方形。

这里的问题是冗余,我的枚举现在也将具有 OVAL 和 ELIPSE,并且 Elipse 类将具有 (CIRCLE, OVAL, ELIPSE TileTypes) 作为它可以匹配的类型。这完全是多余的,我只想说“圆圈”,我可以用这些类型。我想 Tiles 可能有 TileType baseType 和 TileType actualType。

  1. 某种形式的行为组合。忘记 Tile 子类,只需给 Tiles 方法和 List 的实例变量。然后,在运行时我们可以说 someTile.addCanMatch(new CircleMatchBehavior())。这似乎很愚蠢,因为我会有一堆课程只是说你可以匹配特定的形状。

总之,我想要完成的是让多个对象类型能够与任意数量的不同类型进行交互。问题是,我应该为 Type 使用什么。这里可以使用 GetType 吗?枚举?还是有人会推荐更好的策略?我试图尽可能笼统,这些图块不应该对其他图块有任何硬编码依赖,并且必须能够随时更改它们可以与之交互的对象。假设我创建了一个新的 Tile 子类 pentagon... 嗯,Pentagons 可以与 Squares、Circles 和 Pentagons 匹配。我的实现很容易,但有些事情告诉我这是一种肮脏的 OOP 实践。

我觉得我必须使用类型/枚举,因为我不想说 thisTile.addCanMatch(Tile someOtherObject)。这太具体了,我希望 thisTile 能够与作为特定类实例的所有图块匹配。

【问题讨论】:

  • 恐怕我不是一个足够熟练的设计师,无法为您推荐一个完整的设计,但如果圆形和椭圆形的方式没有实际的功能差异操作,最好让所有实例都是Tile 类,然后设置某种Behavior 属性。棋盘上的所有圈子可能都可以共享同一个Behavior 对象,然后当规则更改一回合时,您可以在该Behavior 对象上设置更改。如果我了解情况,这可能会减少代码重复。
  • 如果我的理解正确,这有点像我的#2 行为组合。我在这里担心的是行为爆炸。例如,MatchesWithCirclesAndSquaresBehavior。看看我在说什么?如果有 5 种类型的 Tiles,那么我们有 5 选 1 加 5 选 2 加 5 选 3 加 5 选 4 加 5 选 5 的行为组合。这似乎比 GetType 更糟糕。但是,我可能会误解您的建议中的某些内容。 Tiles 自己做任何他们想做的事情,一个人可能会做一个爆炸动画等。这仅适用于 tile 可以与另一个匹配的情况。
  • 我并不是建议 Behavior 是一个枚举或一个不可修改的类。您可以通过多种方式对其进行子类化,但主要我只是希望它有一个内部集合,表示它的类型(Circle)和它可以匹配的类型(Square)。然后,您可以以某种面向数据的方式进行更改。我绝对同意应该避免任何会导致大量 if/else/switch 块的事情,以支持良好的对象设置,但我们也希望避免编写新类来解释一些不太新的行为变化。

标签: c# oop object reflection instanceof


【解决方案1】:

我知道这个问题已经被回答和接受了,但是我曾经做过一次这样的事情,我想我应该在这里发布代码。

    public class TypeMatchManager
    {
        private Dictionary<Type, List<Type>> savedMatches = new Dictionary<Type, List<Type>>();

        public TypeMatchManager() { }

        public void AddMatch(Type firstType, Type secondType)
        {
            this.addToList(firstType, secondType);
            this.addToList(secondType, firstType);
        }

        public void DeleteMatch(Type firstType, Type secondType)
        {
            this.deleteFromList(firstType, secondType);
            this.deleteFromList(secondType, firstType);
        }

        public bool CanMatch(Type firstType, Type secondType)
        {
            List<Type> firstTypeList = this.findListForType(firstType);
            List<Type> secondTypeList = this.findListForType(secondType);
            return (firstTypeList.Contains(secondType) || secondTypeList.Contains(firstType));
        }

        private void addToList(Type firstType, Type secondType)
        {
            var matchingTypes = this.findListForType(firstType);
            if (!matchingTypes.Contains(secondType))
            {
                matchingTypes.Add(secondType);
            }
        }

        private void deleteFromList(Type firstType, Type secondType)
        {
            var matchingTypes = this.findListForType(firstType);
            if (matchingTypes.Contains(secondType))
            {
                matchingTypes.Remove(secondType);
            }
        }

        private List<Type> findListForType(Type type)
        {
            foreach (var keyValuePair in savedMatches)
            {
                if (keyValuePair.Key == type)
                {
                    return keyValuePair.Value;
                }
            }
            savedMatches.Add(type, new List<Type>());
            return findListForType(type);
        }
    }

该类的设计使您提供哪种类型的参数无关紧要;它检查 type1.list 有 type2 和 type2.list 有类型。

一个简单的例子:

        typeManager.AddMatch(a, b);
        Console.WriteLine(typeManager.CanMatch(a, b)); // True
        typeManager.DeleteMatch(b, a);
        Console.WriteLine(typeManager.CanMatch(a, b)); // False
        Console.WriteLine(typeManager.CanMatch(a, c)); // False
        typeManager.AddMatch(a, c);
        Console.WriteLine(typeManager.CanMatch(a, c)); // True
        Console.WriteLine(typeManager.CanMatch(a, d)); // False
        typeManager.AddMatch(b, d);
        Console.WriteLine(typeManager.CanMatch(a, d)); // False
        Console.WriteLine(typeManager.CanMatch(d, b)); // True
        typeManager.DeleteMatch(d, b);
        Console.WriteLine(typeManager.CanMatch(d, b)); // False
        Console.WriteLine(typeManager.CanMatch(b, d)); // False

【讨论】:

  • 我基本上就是这么想的。非常简洁大方,而且小巧。感谢您的意见!
【解决方案2】:

如果类似类型的所有形状总是共享一个行为,那么不将该行为存储在“每个实例”级别上是有意义的。相反,您可以拥有一个“CanMatchManager”,它存储按形状类型索引的列表字典。然后,当一个圆圈尝试比较匹配时,它会从 MatchManager 请求它可以匹配的类型。或者,MatchManager 可以接收两个形状,并确定它们是否匹配。这是Mediator Pattern

【讨论】:

  • 有趣,比如: CanMatchManager::canMatch(Tile one, Tile two) { return dictionary[one].contains(two)} 其中字典将 Tile Type 映射到可匹配类型的列表。然后,如果我想添加交互,我可以说 tileManager.addInteraction(Tile one, Tile two),它会像 { dict[one].add(two), dict[two].add(one) } 一样更新。就所需的内存而言,这似乎比我正在做的更有效(从实例中获取,交给经理)。但它仍然使用 GetType 对吗?
  • 我真正的问题是“是否可以使用 GetType”来解决这类问题。或者将我的程序重组为不使用类名(类型)是否有意义。我的一部分是说“如果它有效,并且代码不复杂,在这种情况下可能没问题”,但也说“Instanceof 和它的类似糟糕设计的臭味”。为了详细说明第一条评论,字典将是 Dictionary > 地图。我们如何获得类型? someObject.GetType() 和 typeOf(SomeClass)
  • 它仍在使用 GetType 但可以轻松地使用字典。至少,因为它是中心的,您可以轻松地将其工作方式切换到 Enum。我对 Enum 类型的最大问题是,没有一种好的方法可以让一个类静态地说出它的 Enum 是什么,并且在某些时候,感觉就像复制一个已经到位的系统。此外,对于类型,您始终可以使用反射来让您的经理也可以使用继承来工作......我编辑了我的答案以链接到模式文档。
  • 我非常喜欢您的解决方案 - 因为我在类型(类)级别工作,而不是单个对象,所以让类型匹配管理器处理这个问题是有意义的。 Tiles 不应该关心任何其他 Tiles,只有游戏才关心它们是否可以匹配(一些 Tiles 具有特殊的交互,但可以处理)。此外,它将 Type 从对象中取出,从而最大限度地减少了它的使用。我觉得如果我要使用 Types,我应该将它们放在一个经理类中,而不是到处乱扔。我会让这个开放时间更长一点,以便更多的想法,
  • 但我可能会接受这个答案。在这种情况下我也不喜欢 Enums ......我已经有了类名,为什么我需要复制它们?枚举还有其他用途,但在这里似乎并不合适。所以似乎在这样的问题中,使用类名(类型)是可以的,只要它有意义并且它是干净的(MatchManager 非常干净,MatchManagers 方法的调用者并不关心 MatchManager 如何决定两个图块是否可以匹配,它只是想知道他们是否可以)。没有讨厌的 if/else/switch 或类似的东西
猜你喜欢
  • 1970-01-01
  • 2011-09-22
  • 1970-01-01
  • 2014-12-01
  • 2017-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-06
相关资源
最近更新 更多