代码之家  ›  专栏  ›  技术社区  ›  John Rasch

这是滥用类型系统吗?

  •  3
  • John Rasch  · 技术社区  · 15 年前

    我正在做一个游戏的业余项目 Baroque Chess . 对于那些没有下棋的人来说,它和国际象棋有着相同的基本规则,但是移动和捕获的方法是不同的。

    当然,我为游戏创建了标准类: GameState ,请 Board , Square 以及为继承自 BasePiece .

    每件作品都有两种主要的虚拟方法, GetPossibleMoves(Board board) GetCapturedSquares(Board board, Square toSquare) .

    现在,其中一件, the Imitator ,通过“模仿”它捕获的片段来捕获片段。例如,一个跳远者可以通过跳跃来捕捉碎片。这意味着模仿者可以跳过敌人的跳远者来抓住他们(但不能跳过其他任何东西)。

    我完成了 GetCapturedSquares() 除了模仿者以外的所有作品的功能(这绝对是最棘手的作品)。

    我对模仿者的基本算法是:

    • 找到所有有敌人碎片的广场
    • 对于每一个敌人…
      • 在与模仿者相同的位置创建模拟件
      • 找到有效的捕获,如果它是移动到选定方块的模拟块
      • 确认敌方方阵在已捕获方阵列表中

    因为我已经为其他片段的移动编写了代码,所以我想我只需实例化新片段并使用它们 getCapturedQuares()。 方法根据敌人是哪一种类型。为此,我设置了一个 Dictionary 正如你在这里看到的地图 System.Type 到所述类型的实例化对象:

    var typeToPiece = new Dictionary<Type, BasePiece>() 
    {
        {typeof(Pincer), new Pincer() { Color = this.Color, CurrentSquare = this.CurrentSquare}},
        {typeof(Withdrawer), new Withdrawer() { Color = this.Color, CurrentSquare = this.CurrentSquare }},
        {typeof(Coordinator), new Coordinator() { Color = this.Color, CurrentSquare = this.CurrentSquare }},
        {typeof(LongLeaper), new LongLeaper() { Color = this.Color, CurrentSquare = this.CurrentSquare }},
        {typeof(King), new King() { Color = this.Color, CurrentSquare = this.CurrentSquare }},
    };
    //...
    var possibleMoves = typeToPiece[enemySquare.Occupant.GetType()].GetPossibleMoves(board, toSquare);
    

    这样做会让我感到内心肮脏。创建一个 enum string 它将工件类型表示为字典键,还是真的不重要?有不同的方法来处理这个吗?我认为这样没问题,但我有兴趣听听你的想法。

    4 回复  |  直到 15 年前
        1
  •  7
  •   Mehrdad Afshari    15 年前

    我想你应该加一个 abstract 方法到 BasePiece 它“克隆”当前片段并返回模拟片段。

    你会 override 这种方法适用于每种工件类型。要在它们之间共享代码,可以添加 protected 方法复制共享属性到传递给它的实例的基类:

    abstract class BasePiece {
       protected BasePiece(BasePiece pieceToClone) {
          this.Color = pieceToClone.Color;
          this.CurrentSquare = pieceToClone.CurrentSquare;
       }
       public abstract BasePiece GetSimulatedClone();
    }
    
    class King : BasePiece {
       protected King(King pieceToClone) : base(pieceToClone) { }
       public King() { }
       public override BasePiece GetSimulatedClone() {
           return new King(this);
       }
    }
    

    一般来说,当您根据变量的类型进行切换时,您应该三思而后行,看看您是否可以改为多态性。

        2
  •  1
  •   recursive    15 年前

    我觉得这有点优雅:

        private abstract class Piece {}
        private class King : Piece { }
        private class Imitator : Piece { }
    
        private void main(object sender, EventArgs e) {
            Piece x;
            x = CreateNewPiece(new King());
            x = CreateNewPiece(new Imitator());
        }
    
        private T CreateNewPiece<T>(T piece) where T : Piece, new() {
            return new T();
        }
    

    它依赖于 new() 用于实例化类型变量的泛型约束。

        3
  •  1
  •   StriplingWarrior    15 年前

    我个人同意你可以做你正在做的事情,因为这是一个小的爱好项目。如果你真的想避开你描述的问题,你可以创建一个单独的 Mover 处理围绕不同部分的实际移动的逻辑的对象。然后,您不需要询问工件可以做什么动作,而是将该工件的位置信息以及板状态信息传递给移动器,移动器告诉您该工件可以做什么动作。然后,与一个模仿者相关联的移动器可以是您描述的所有其他移动器的组合,但不需要在运行中创建假“片段”。

    我提出的另一个建议是,像这样改变你的逻辑,这与逻辑比你的模型更相关:

    • 对于每种工件类型(或移动式)
      • 在与模仿者相同的位置创建模拟件
      • 找到有效的捕获,如果它是移动到选定方块的模拟块
      • 只有在敌方的占领者是你正在检查的碎片类型的地方才能保留捕获物。

    这是一个细微的差别,但会显著减少所需的计算量。

    更新

    递归的评论让我意识到,对于mover层的工作方式,我可能还不够清楚。这个想法是要有一个主宰者,钳子,等等,他们知道一个特定的工件类型的移动。因为它们是与一个作品的类型而不是作品本身联系在一起的,所以它们甚至可以是单件作品。每种工件类型都可以有一个mover字段指向其mover,然后您的业务逻辑可以直接调用mover的getpossiblemoves方法,或者工件的getpossiblemoves方法可以简单地调用mover的方法,将自己作为参数传入。模仿者知道向对方的移动者询问其可能的移动,然后根据这些移动者是否会攻击与该移动者相关的移动类型来过滤这些移动。

    您可能拥有与当前系统中几乎相同的代码,但是每个工件的代码实际上只关注于表示该工件的信息(位置、颜色等),而实际确定工件移动方式的代码将移动到单独的类层。每门课都有一个目的。

        4
  •  1
  •   Pavel Minaev    15 年前

    如果不涉及这个特定的问题,字典的概念就没有什么本质上的错误,它允许您根据对象的类型查找对象。实际上,.NET FCL已经提供了这样的类型-它被称为 KeyedByTypeCollection<T> .对于这样的事情,它可能更好,因为它确保对象的键是由返回的类型 GetType() (而不是其他随机类型),并且不允许添加同一类型的两个对象。