面向对象设计中避免两个对象相互引用的两种方法

3
我正在编写一个Java棋类游戏,试图解决设计问题。以下是我目前的类:

游戏:由两个玩家对象和一个棋盘对象组成

玩家:

棋盘:由一个2D数组(8 x 8)的方格对象组成,每个方格上可能有/可能没有棋子

国王、皇后、车、象、马、兵:

棋子:以上棋子的超类,共享功能位于此处

这里是我不喜欢的地方:我的Square类中有一个名为“board”的字段,以便我可以引用给定方格所属的棋盘。我不喜欢这种设计,因为它会导致克隆问题。

因此,我想重新编写一些棋子类的功能,而不进行这种回溯。让棋盘能够访问其每个方格很好,但我不想为Square类设置getBoard()函数。

以下是需要重写的一个函数示例:

public Square getSquareOneMoveAway(Square start, int heightChange, int widthChange) {

    Square candidateSquare = start.getBoard().getNearbySquare(start, heightChange, widthChange);

    if (candidateSquare != null) {
        if (candidateSquare.isEmpty()) {
            return candidateSquare;
        } else if (! candidateSquare.getPiece().getColor().equals(start.getPiece().getColor())) {
            return candidateSquare;
        }
        else {
            return null;
        }
    }
    else {
        return null;
    }
}

注意,在我的Player类中,我有像canMovePiece(Square start,Square end,Board board),movePiece(Square start,Square end,Board board)等方法:
我应该将这个方法移动到Board类中,这样我就可以访问板对象,而不必从方块到板吗?
非常感谢任何想法,bclayman

2
这可能更适合于代码审查,尽管代码审查人员会因我这样说而大声反对。一个“棋盘”就是那样,一个棋盘。它不知道上面正在玩什么。一个棋类的棋盘可能会...或者你可以有一个有棋盘并根据棋子的移动能力知道可以进行哪些移动的类,或者规则可能封装在其他地方,因为有一些改变了那些能力的国际象棋的变种。有很多选择. - Dave Newton
@DaveNewton 我们只会在你因为那个原因投票关闭,或者将明显与 CR 不相关的帖子推荐到 CR 时才大喊。感谢您仍然有勇气推荐 CR! - Simon Forsberg
1
我认为你需要对你的代码进行全面审查。包括所有相关类的代码,并让Code Review的专业人员施展他们的魔力!(假设你已经实现了你的设计并且它按照预期工作 - Simon Forsberg
3个回答

2
在我看来,你过度设计了这个问题。用另一个类来表示棋盘上的方块是否真的有任何优势吗?如果没有(我认为没有),我会只去掉Square类。
class Board {
    public static final int MAX_ROWS = 8;
    public static final int MAX_COLS = 8;

    private Piece[][] squares;

    public Board() {
        squares = new Piece[ MAX_ROWS ][ MAX_COLS ];
    }

    // ...
}

我会建立一个名为Position的类,它包含一对行和列的信息。
希望这可以帮到您。

那么你是否只是希望棋盘是由棋子数组组成的,其中许多棋子为空(表示空位)? - anon_swe
1
没错。板子已经固定了,而且也不是很大,所以为什么不呢?除非你在编写内存受到严格限制的嵌入式系统,否则你可以负担得起一个包含(8x8=)64个位置的数组。没什么大不了的。 - Baltasarq

1
下面的ChessBoard定义用于存储棋子,存储它们的位置,使用这些信息来确定给定移动是否有效。
class ChessBoard implements Board {
    Piece[][] pieces; // null if vacant at that position

    @Override
    public boolean isValidMove(Player player, Square start, Square end) {
        Piece pieceStart = pieces[start.row][start.col];
        Piece pieceEnd = pieces[end.row][end.col];

        return (
            pieceStart != null &&
            pieceStart.getColor() == player.getColor() &&
            pieceStart.isValidTransition(start, end) &&
            (pieceEnd == null || pieceStart.canDefeat(pieceEnd))
        );
    }
}

请注意,决定移动是否有效的逻辑被封装在正在移动的棋子中。
class Pawn implements Piece {

    @Override
    public boolean isValidTransition(Square start, Square end) {
        int dH = end.col - start.col;
        int dW = end.row - start.row;
        // return if is valid transition for a pawn in terms of dH and dW
    }

    @Override
    public boolean canDefeat(Piece otherPiece) { 
        return (
            this.getColor() != otherPiece.getColor() &&
            this.getStrength() > otherPiece.getStrength()
        );
    }

    @Override
    public int getStrength() { ... }

    @Override
    public Color getColor() { ... }
}

方块类是一个简单的2元组,包含行和列。

class Square {
    int row, col;
}

0

我是一个实用主义者,所以我不会将代码片段作为类,而只使用枚举。因此:

enum Pieces 
{
   Pawn,
   Rook,
   Knight
   // etc
};

然后我会创建一个名为“Rule”的接口,就像这样:
interface Rule
{
   void addRule(Pieces p);
};

通过让一个类实现规则,我可以决定游戏中(或者更好的是关于游戏)单位之间如何交互。这个类可以是PawnRule(兵的规则)、QueenRule(皇后的规则)等等。这样做可以将尽可能多的规则添加到游戏中并利用它们。这不仅有利于扩展性,而且为游戏本身带来了可行的基础。

class PawnRule implements Rule
{
    private boolean pFirstMove = true;
    // etc
    void addRule(Pieces p)
    {
        if (Square.hasSpace(Space.FRONT) * 2 && pFirstMove)
        {
            // where renderer holds 2D/3D data pertaining to the piece
            rendrerer.getModel("Pawn").moveByTwo(); // somehow
        }
    }
}

在设计方面,需要考虑的因素还有很多;可能枚举并不是你要寻找的。我们也不知道游戏是基于文本、2D 还是 3D 的。通常,如果是基于文本的话,实现这些东西会更容易,但如果是 3D 的话,则更为复杂。我建议绘制一个类图和甚至是一个通信图,以便了解棋盘游戏中的类/枚举/接口如何相互交互。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接