Reputation: 123
I'm making a chess game in C#. I've got 2 classes, Field and Piece:
public class Field
{
// the piece that is standing on this field
// null if no piece is standing on it
public Piece piece { get; set; }
}
public class Piece
{
// the field this piece is standing on
public Field field { get; set; }
}
When a piece moves, this method is called (in class Piece):
public void Move(Field field)
{
this.field = field;
field.piece = this;
}
This doesn't seem to be good coding, because everytime I change the field property, I also have to change the piece property for that field. I do need both properties though, because elsewhere in my code, I need them both to do checks etc (e.g. what's the field this piece is on and by what piece is this field taken).
My question: is this completely ok, is it a bad code smell or is it totally wrong? What would be a good solution to solve this?
Any advice? Thanks in advance!
Upvotes: 3
Views: 2261
Reputation: 4783
No! This relates to concept of circular dependency. Although applied for modules, this may very well be seen as precursor for such.
More concretely, this is an ideal example for mutually recursive objects. Conceptually, if you substitute (semi-pseudocode)
public class Field
{
public Piece piece {
public Field field {
public Piece piece {
...
}
}
}
}
That's because the objects are defined in terms of each other. Then theoretically you can do something like
this.field.piece.field.piece...
Upvotes: 0
Reputation: 37020
The problem I see here is that you have Piece.field
and Field.piece
as public properties. This means that others can set these properties without updating the corresponding one.
Additionally, when you move a piece from one field to another, you don't remove the piece from the previous field, and we allow pieces to move to occupied squares, which will result in multiple pieces referring to the same field, but the field will only refer to the last piece placed there.
To address these, I would make the properties read only (with a private setter), forcing clients to call the corresponding Set
or Move
method to change them. Then, in this method, we can verify that the field we're moving to is not occupied (if it is, we simply throw an exception - the client must check this first before calling Move
), and that we clear the Piece
from the Field
we moved from.
The validation work can be done in either the Field
or Piece
class, or both. I put it all in the Field
class to simplify things.
Even still, there are problems with this. You can call Field.SetPiece(piece)
directly (instead of Piece.MoveTo(field);
), which will leave the piece with a null
value for Field
. So this is only a slight improvement, but not the ideal solution. See below for a better idea.
public class Field
{
public Piece Piece { get; private set; }
public bool Occupied => Piece != null;
public void ClearPiece()
{
// Remove this field from the piece
if (Piece?.Field == this) Piece.MoveTo(null);
// Remove the piece from this field
Piece = null;
}
public void SetPiece(Piece piece)
{
if (piece != null)
{
if (Occupied)
{
throw new InvalidOperationException(
$"Field is already occupied by {Piece}.");
}
// Remove piece from the piece's previous field
if (piece.Field?.Piece == piece)
{
piece.Field.ClearPiece();
}
}
Piece = piece;
}
}
public class Piece
{
public Field Field { get; private set; }
public void MoveTo(Field field)
{
field.SetPiece(this);
Field = field;
}
}
After thinking a little more about this, I think a better solution would be to have a GameManager
class that handles all the validation and movement, and then we can make the Field
and Piece
classes "dumb".
This makes sense because there is a lot more validation to be done before setting a Piece
on a Field
. Is it ok to move this piece to the location (i.e. if the King is in check and this doesn't block it, then it's not allowed). Is the Field
a valid landing spot for the piece based on the piece's move rules (i.e. a horizontal position for a bishop would not be allowed)? Is there anything blocking the path of the piece to get to the destination? Is the destination occupied by another piece belonging to the same player? Many things to evaluate before moving a piece.
Additionally, this would allow us to reuse the Piece
and Field
classes in other types of games, which may have a different set of rules, and a different GameManager
to enforce them.
Upvotes: 2