棋盘和棋子之间相互依赖

关于相互依赖的问题有很多类似的问题,但每个问题都让我不确定自己的设计。

我正在写一个国际象棋程序来学习Scala。 董事会与其董事会之间的密切关系使我怀疑一个Piece对象是否应该包含对它所属的董事会的引用。 这是我用Java编写国际象棋程序时的做法。

但是,这意味着一块电路板只有在其部件才能完全定义,反之亦然。 如果董事会实例是一个变量,您可以在构建董事会时增加一些变量,但这种代码性不成问题,但这不符合不变性。

有关:

这里的方法似乎建议定义棋盘对象中棋子移动的所有规则:https://gamedev.stackexchange.com/questions/43681/how-to-avoid-circular-dependencies-between-player-and-world

这里表决得分最高的答案也有类似的建议:两个互相依赖的对象。 那不好吗?

上述链接的选择答案是不同的 - 它将相互依赖从类定义移动到接口。 我不明白为什么这样更好。

这里的设计反映了我目前的做法 :https://sourcemaking.com/refactoring/change-bidirectional-association-to-unidirectional

我的代码:

abstract class Piece(val side: Side.Value, val row: Int, val col: Int){
  val piece_type: PieceType.Value //isInstanceOf() could accomplish the same
  def possible_moves(board: Board): List[Move]
}

class Board (val pieces: Array[Array[Piece]]){
  def this(){
    this(DefaultBoard.setup) //An object which builds the starting board
  } 
}

作为一个参数传递一块作品所属的板子会起作用,但感觉不对。

提前致谢!


我花了一些自由来重新设计你的课程。

我注意到的第一件事:你的Piece并不是真正的作品。 假设左上角有一位白人主教。 如果我将它移动到右上角的领域,它会变成另一块吗? - 很明显不是。 因此,棋子在棋盘上的位置不是其身份的一部分。

所以我会重构类Piece

trait Piece {
  def side:Side.Value
  def piece_type:PieceType.Value
}

(我在这里使用了一个特征而不是抽象类,这样我就可以让实现者了解如何实现这两种方法。)

在这个过程中丢失的信息应该放在一个不同的类型中:

case class PiecePlacement(piece:Piece, row:Int, col:Int) {
  def possible_moves(board:Board):Seq[Move] = ??? // Why enfore a list here?
}

现在我们可以定义一个这样的板子:

case class Board(pieces:IndexedSeq[IndexedSeq[Piece]] = DefaultBoard.setup)

(请注意,我是如何用默认参数值替换辅助构造函数的,并且还使用了不可变的IndexedSeq而不是可变Array 。)

如果你现在想要在棋子和棋盘的位置之间有依赖关系,你可以这样做:

  • 将板添加到PiecePlacement
  • case class PiecePlacement(piece:Piece, row:Int, col:Int, board:Board) {...}

  • 让董事会创建展示位置实例:
  • case class Board(...) { def place(piece:Piece, row:Int, col:Int):(Board,PiecePlacement) = ??? }

    请注意, place的返回值不仅返回新的PiecePlacement实例,还返回新的Board实例,因为我们想要处理不可变实例。

    现在,如果你看看这个问题,应该提出这个问题,为什么place甚至会返回PiecePlacement 。 来电者有什么好处? 这几乎只是电路板内部的信息。 因此,您可能会重构place方法,使其仅返回新的Board 。 然后,你可以完全没有Placement类型,从而消除相互依赖。

    另一件你可能想要注意的place方法不能实现。 返回的Board必须是新实例,但返回的PiecePlacement必须包含新的Board实例。 由于新的Board实例也包含PiecePlacement实例,因此永远不可能以完全不可改变的方式创建它。

    所以我会真正遵循@JörgWMittag的建议并摆脱相互参照。 开始为你的棋盘和棋子定义特质,并且只包括绝对最少的必要信息。 例如:

    trait Board {
      def at(row:Int, col:Int):Option[Piece]
      def withPieceAt(piece:Piece, row:Int, col:Int):Board
      def withoutPieceAt(row:Int, col:Int):Board
    }
    
    sealed trait Move
    case class Movement(startRow:Int, startCol:Int, endRow:Int, endCol:Int) extends Move
    case class Capture(startRow:Int, startCol:Int, endRow:Int, endCol:Int) extends Move
    
    sealed trait PieceType {
      def possibleMoves(board:Board, row:Int, col:Int):Seq[Move]
    }
    object Pawn extends PieceType {...}
    object Bishop extends PieceType {...}
    
    sealed trait Piece {
      def side:Side.Value
      def pieceType:PieceType
    }
    case class WhitePiece(pieceType:PieceType) {
      def side:Side.White
    }
    case class BlackPiece(pieceType:PieceType) {
      def side:Side.Black
    }
    

    现在您可以开始编写使用这些特征的代码来推断潜在的移动等。另外,您可以编写实现这些特征的类。 您可以从简单的实施开始,然后根据需要进行优化。

    例如:每个董事会职位只有13个可能的状态。 每个棋子类型一个,两面两个,加上一个空的状态。 这些州是非常可枚举的,所以你可以通过枚举来优化它们。

    另一个潜在的优化:由于电路板位置仅需要4位来建模,所以电路板的整行在编码时会适合Int变量。 因此,整个董事会的状态可以表示为只有八个Int ,甚至可以是四个Long 。 这种优化可以平衡性能(位移),有利于内存使用。 因此,这种优化对于生成大量Board实例并有陷入OutOfMemoryError危险的算法会更好。

    通过将板子和零件建模为特征而不是类,您可以轻松地交换实现,与它们一起玩耍,并查看哪种实现最适合您的使用情况 - 而无需更改一些利用该算法的算法板和件。

    底线:仅在需要时才引入方法和变量等内容。 您不写的每行代码都是一行不能包含错误的代码。 不要担心对象之间的相互依赖关系,当它们不是绝对必要时。 而在棋盘模型的情况下,它们绝对没有必要。

    首先关注简单。 每一种方法,类别和参数都应该证明它的存在。

    例如,在我提出的模型中,位置总是有两个参数: rowcol 。 由于只有64个可能的职位,我会争辩做一个Position类型。 例如,它甚至可以是将被编译为IntAnyVal类型。 然后,你不需要嵌套结构来存储板。 您只能存储64个电路板放置信息对象,就是这样。

    只介绍必要的最低限度,并在需要时延长。 在极端的情况下,只有在没有它们的情况下才能继续下去,从空的特质开始并添加方法。 当你处理它时,为每一种方法编写单元测试。 这样,你应该找到一个好的,干净的和可重用的解决方案。 可重用性的关键是:尽可能避免功能。 您引入的每个功能都会限制多功能性。 放入刚才所要求的内容。

    链接地址: http://www.djcxy.com/p/51043.html

    上一篇: mutual dependency between chessboard and its pieces

    下一篇: Best OOP way to represent chess pieces