【问题标题】:Is this an abuse of the type system?这是对类型系统的滥用吗?
【发布时间】:2010-12-05 03:12:45
【问题描述】:

我正在开发游戏Baroque Chess 的爱好项目。没玩过的人,基本规则和国际象棋一样,只是走法和抓法不同。

我自然而然地为游戏创建了标准类:GameStateBoardSquare,并为从 BasePiece 继承的每个部分指定了一个类。

每个部分都有 2 个主要的虚拟方法,GetPossibleMoves(Board board)GetCapturedSquares(Board board, Square toSquare)

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

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

我模仿者的基本算法是:

  • 找到所有有敌方棋子的方格
  • 对于每个敌方棋子...
    • 在与模仿者相同的位置创建模拟作品
    • 如果是模拟棋子移动到选定的方格,则查找有效捕获
    • 验证敌方方格是否在捕获方格列表中

因为我已经为其他棋子的动作编写了代码,所以我想我只需实例化新棋子并根据敌人的棋子类型使用它们的GetCapturedSquares() 方法。为此,我设置了一个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);

这样做让我觉得内心很肮脏。创建一个将片段类型表示为字典键的enumstring 是否更合适,还是真的没关系?有没有不同的方法来处理这个?我认为它的方式很好,但我很想听听你的想法。

【问题讨论】:

    标签: c# oop types


    【解决方案1】:

    我认为您应该在BasePiece 中添加一个abstract 方法,以“克隆”当前片段并返回模拟片段。

    你会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】:

    这对我来说更优雅一点:

        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() 泛型约束来实例化一个类型变量。

    【讨论】:

    • 我认为这实际上可能行不通,因为您会将“Piece”类型的变量传递给函数,它不一定满足“new()”。
    • 嗯。显然,即使您使用构造函数使基类具体化, T 也不会采用值的实际类型,而是变量的声明类型。我放弃了。
    • 有一种方法可以做你想做的事情,但它涉及使整个类通用。但是,对于这个特定用例,更好的方法是制作 T CreateNewPiece(Piece piece),然后调用 CreateNewPiece()。
    【解决方案3】:

    我个人同意你可以做你正在做的事情,因为这是一个小爱好项目。但是,如果您真的想解决您所描述的问题,您可以创建一个单独的 Mover 对象层来处理围绕不同部分的实际移动的逻辑。然后,您无需询问棋子可以进行什么移动,而是将该棋子的位置信息以及棋盘状态信息传递给 Mover,Mover 会告诉您该棋子可以进行哪些移动。然后,与模仿者关联的 Mover 可以是您描述的所有其他 Mover 的组合,但无需动态创建假“碎片”。

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

    • 适用于每种工件类型(或动子类型)
      • 在与模仿者相同的位置创建模拟作品
      • 如果是模拟棋子移动到选定的方格,则查找有效捕获
      • 仅保留敌方方格的占领者属于您正在检查的棋子类型的捕获

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

    更新

    Recursive 的评论让我意识到我可能对 Mover 层的工作方式还不够清楚。这个想法是有一个 KingMover、PincerMover 等等,它们知道特定棋子类型的移动。由于它们与作品的类型而不是作品本身相关联,因此它们甚至可以是单例。每个片段类型都可以有一个指向其移动器的 Mover 字段,然后您的业务逻辑可以直接调用该 Mover 的 GetPossibleMoves 方法,或者您的片段的 GetPossibleMoves 方法可以简单地调用 Mover 的方法,将自身作为参数传入。 ImitatorMover 会知道向其他类型的移动器询问其可能的移动,然后根据它们是否会攻击与该移动器相关的类型来过滤这些移动。

    您将拥有与当前系统中几乎相同的代码,但每个 Piece 的代码实际上可以只专注于表示该 Piece 的信息(位置、颜色等),而用于实际确定一个 Piece 的代码移动将被移动到一个单独的类中。每个班级都有一个目的。

    【讨论】:

    • 那么移动者必须知道所有棋子的所有捕获规则。
    • 根据我的描述,我明白您为什么会这么认为。请查看我的更新答案。
    • 有机会我会更新逻辑!
    【解决方案4】:

    如果不涉及这个具体问题,那么让您按类型查找对象的字典的想法在本质上并没有错。事实上,.NET FCL 已经提供了这样一种类型——它被称为KeyedByTypeCollection&lt;T&gt;。对于这样的事情可能会更好,因为它确保对象的键是 GetType() 返回的类型(而不是其他随机类型),并且不会让您添加两个相同类型的对象。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-14
      • 2011-04-07
      • 2012-06-13
      • 2010-12-04
      • 1970-01-01
      • 1970-01-01
      • 2010-09-06
      • 1970-01-01
      相关资源
      最近更新 更多