Question

This question is about design and the implications of many objects in a system holding references to the same object. I've encountered this issue in other projects, but I think it's particularly applicable to my current project. Is there any guidance as to whether either design should be avoided for reasons unforeseen?

The examples involve a chess program I'm working on. They're not representative of my actual code, so no need to comment on the quality of the code which has obviously been sacrificed for the purpose of brevity.

First design

public class Piece
{
    private Board board;
    private PieceType type;
    private Color color;
    private Square location;

    public Piece(PieceType type, Color color, Board board, Square square)
    {
        // assignments to members
    }

    public Move[] moves()
    {
        type.moves(board, location);
    }
}

public final class Board // Board is immutable
{
    // Would have to manage pieces with duplicate squares
    private List<Piece> pieces;

    public Board placePiece(PieceType type, Color color, Square square)
    {
        Piece p = new Piece(type, color, this, square);
        pieces.add(piece);
    }

    public Move[] moves(Square fromLocation)
    {
        Piece p = pieces.get(fromLocation);
        return p.moves();
    }
}

I abandoned this design where each Piece held a reference to the Board at construction. I think it's awkward for the Piece's to maintain this reference when they only need the Board in its current state at the moment they're asked to determine their available moves. I tend toward injecting dependencies in the constructor instead of methods, because it removes context from clients. However, there's something I don't like about this design, especially the fact that the pieces would have to be updated with the new board every time the board changes. Since Board is immutable, the Pieces will maintain references to Boards with expired states. Creating a new Board entails creating all new Pieces too, which seems like a lot of unnecessary computation; especially considering many of them won't even have moves invoked in a given turn, i.e., won't even take advantage of the most current Board.

Second design

Would it be better to store the location information in the Board and pass in the board and location to the Piece's moves method?

public class Piece
{
    private PieceType type;
    private Color color;

    public Piece(PieceType type, Color color)
    {
        // assignments to members
    }

    // Can pass in 
    public Move[] moves(Board board, Square location)
    {
        type.moves(board, location);
    }
}

public class Board
{
    // No duplicate squares allowed
    private Map<Square, Piece> pieces;

    public Board placePiece(PieceType type, Color color, Square square)
    {
        Piece p = new Piece(type, color);
        pieces.put(square, piece);
    }

    public Move[] moves(Square fromLocation)
    {
        Piece p = pieces.get(fromLocation);
        return p.moves(this, fromLocation);
    }
}

The only other way I can think to use the first design is not to pass a reference to a Board in the Piece's constructor, rather pass a proxy with an interface that matches Board, but itself is updated with the current board when changes are made. That way, all pieces would always reference the most recent board.

Was it helpful?

Solution

This is, unfortunately, a highly opinionated question, and there is no perfect answer here. However, I believe many would agree that your insights in creating Design 2 after Design 1 are, on the whole, strong and thus make Design 2 objectively better in a number of ways.

You hit the nail on the head nice and heard with removing the Board reference from the Piece definition, and the Square as well. A good OOO design principle is that Objects should only store references to that which conceptually defines them. The Board, and Square (BoardPosition?) do not define the Piece.

The moves(...) method is a tricky one. While the Piece class seems nice, I would argue that the Board class is better, but still not enough. If you intend to create a fully accurate Chess implementation, you will have to design for En passant, Castling, and Stalemate conditions, all of which depend not only on the current Board state, but previous Board states as well - the transactional history of the ChessGame, if you will.

I could go on and on here but you deserve to learn more of this for yourself. So, I will leave a few more questionably objective nuggets for your consumption:

  • Be sure to use Enums vigilantly! PieceType and Color are perfect candidates for this. Enums make your code precise and clear, and tend to turn nasty Runtime errors into easy Compilation errors for equivalent designs.
  • Always design with your end goals in mind. If you plan on doing some sort of AI experimentation with these classes, revisit your decision to make Board immutable. This may be more prohibitive than you want for fast analysis.
  • In direct contradiction with my previous point, don't worry about interfaces until you know you need at least two different implementations, and you know what they are. Speculative abstraction is more harmful than short-term limitations.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top