14

I am creating a board game (such as chess) in Java, where each piece is its own type (like Pawn, Rook etc.). For the GUI part of the application I need an image for each of these pieces. Since doing thinks like

rook.image();

violates separation of UI and business logic, I will create a different presenter for each piece and then map the piece types to their corresponding presenters like

private HashMap<Class<Piece>, PiecePresenter> presenters = ...

public Image getImage(Piece piece) {
  return presenters.get(piece.getClass()).image();
}

So far so good. However, I sense a prudent OOP guru would frown upon calling a getClass() method and would suggest using a visitor for example like this:

class Rook extends Piece {
  @Override 
  public <T> T accept(PieceVisitor<T> visitor) {
    return visitor.visitRook(this);
  }
}

class ImageVisitor implements PieceVisitor<Image> {
  @Override  
  public Image visitRook(Rook rook) {
    return rookImage;
  } 
}

I like this solution (thank you, guru), but it has one significant drawback. Every time a new piece type is added to the application the PieceVisitor needs to be updated with a new method. I would like to use my system as a board game framework where new pieces could be added through a simple process where the user of the framework would only provide implementation of both the piece and its presenter, and simply plug it into the framework. My question: is there a clean OOP solution without instanceof, getClass() etc. which would allow for this kind of extensibility?

lishaak
  • 467

6 Answers6

12

is there a clean OOP solution without instanceof, getClass() etc. which would allow for this kind of extensibility?

Yes there is.

Let me ask you this:

In your current examples you're finding ways to map piece types to images. How does this solve the problem of a piece being moved?

A more powerful technique than asking about type is to follow Tell, don't ask. What if each piece took a PiecePresenter interface and it looked like this:

class PiecePresenter implements PieceOutput {

BoardPresenter board; Image pieceImage;

PiecePresenter(BoardPresenter board, Image image) { this.board = board; this.image = image; }

@Override public void display(int rank, int file) { board.display(image, rank, file); }
}

The construction would look something like this:

rookWhiteImage = new Image("Rook-White.png");
PieceOutput rookWhiteOutPort = new PiecePresenter(boardPresenter, rookWhiteImage);
PieceInput rookWhiteInPort = new Rook(rookWhiteOutPort);
board[0, 0] = rookWhiteInPort;

Use would look something like:

board[rank, file].display(rank, file);

The idea here is to avoid taking responsibility for doing anything that other things are responsible for by not asking about it nor making decisions based on it. Instead hold a reference to something that knows what to do about something and tell it to do something about what you know.

This allows for polymorphism. You don't CARE what you're talking to. You don't care what it has to say. You just care that it can do what you need done.

A good diagram that keeps these in separate layers, follows tell-don't-ask, and shows how to not couple layer to layer unjustly is this:

enter image description here

It adds a use case layer we haven't used here (and can certainly add) but we are following the same pattern you see in the lower right corner.

You'll also notice that Presenter doesn't use inheritance. It uses composition. Inheritance should be a last resort way to get polymorphism. I prefer designs that favor using composition and delegation. It's a bit more keyboard typing but it's a lot more power.

I'd go on but I've talked about my chess adventures before.

candied_orange
  • 119,268
5

Your Model (the figure classes) have a common methods you might need in other context too:

interface ChessFigure {
  String getPlayerColor();
  String getFigureName();
}

The images to be used to display a certain figure get file names by a naming schema:

King-White.png
Queen-Black.png

Then you can load the appropriate image without accessing information about the java classes.

new File(FIGURE_IMAGES_DIR,
         String.format("%s-%s.png",
                       figure.getFigureName(),
                       figure.getPlayerColor)));

I am also interested in general solution for this kind of problems when I need to attach some information (not only images) to a potentially growing set of classes."

I think you should not focus on classes that much. Rather think in terms of business objects.

And the generic solution is a mapping of any kind. IMHO the trick is to move that mapping from the code to a resource that is easier to maintain.

My example does this mapping by convention which is quite easy to implement and avoids to add view related information into the business model. On the other hand you could consider it a "hidden" mapping because it is not expressed anywhere.

Another option is to see this as a separate business case with its own MVC-layers including a persistence layer that contains the mapping.

2

I would create a separate UI/view class for each piece which contains the visual information. Every one of these pieceview classes has a pointer to its model/business counterpart which contains the position and the game rules of the piece.

So take a pawn for example:

class Pawn : public Piece {
public:
    Vec2 position() const;
    /**
     The rest of the piece's interface
     */
}

class PawnView : public PieceView {
public:
    PawnView(Piece* piece) { _piece = piece; }
    void drawSelf(BoardView* board) const{
         board.drawPiece(_image, _piece->position);
    }
private:
    Piece* _piece;
    Image _image;
}

This allows for complete separation of logic and UI. You can pass the logic piece pointer to a game class which would handle the moving of the pieces. The only drawback is that the instantiation would have to happen in a UI class.

0

I would approach this by making Piece generic, where its parameter is the type of an enumeration that identifies the type of piece, each piece having a reference to one such type. Then the UI could use a map from the enumeration as before:

public abstract class Piece<T>
{
    T type;
    public Piece (T type) { this.type = type; }
    public T getType() { return type; }
}
enum ChessPieceType { PAWN, ... }
public class Pawn extends Piece<ChessPieceType>
{
    public Pawn () { super (ChessPieceType.PAWN); }

This has two interesting advantages:

First, applicable to most statically typed languages: If you parametrise your board with the type of piece to xpect, you can't then insert the wrong kind of piece into it.

Second, and perhaps more interestingly, if you're working in Java (or other JVM languages), you should note that each enum value is not just an independent object, but it can have its own class, too. This means that you can use your piece type objects as Strategy objects to customer the behavior of the piece:

 public class ChessPiece extends Piece<ChessPieceType> {
    ....
   boolean isMoveValid (Move move)
    {
         return getType().movePatterns().contains (move.asVector()) && ....


 public enum ChessPieceType {
    public abstract Set<Vector2D> movePatterns();
    PAWN {
         public Set<Vector2D> movePatterns () {
              return Util.makeSet(
                    new Vector2D(0, 1),
                    ....

(Obviously actual implementations need to be more complex than that, but hopefully you get the idea)

Jules
  • 17,880
  • 2
  • 38
  • 65
0

I am pragmatic programmer and i really don't care what is clean or dirty architecture. I believe requirements and it should to be handled simple way.

Your requirement is your chess app logic will be represented on different presentation layers(devices) like on web, mobile app or even console app so you need to support these requirements. You may prefer to use very different colors, piece images on each device.

public class Program
{
    public static void Main(string[] args)
    {
        new Rook(new Presenter { Image = "rook.png", Color = "blue" });
    }
}

public abstract class Piece
{
    public Presenter Presenter { get; private set; }
    public Piece(Presenter presenter)
    {
        this.Presenter = presenter;
    }
}

public class Pawn : Piece
{
    public Pawn(Presenter presenter) : base(presenter) { }
}

public class Rook : Piece
{
    public Rook(Presenter presenter) : base(presenter) { }
}

public class Presenter
{
    public string Image { get; set; }
    public string Color { get; set; }
}

As you have seen presenter parameter should to be passed on each device(presentation layer) differently. That means your presentation layer will decide how to represent each piece. What is wrong in this solution ?

Freshblood
  • 1,517
0

There is another solution which will help you to abstract UI and domain logic completely. Your board should to be exposed to your UI layer and your UI layer can decide how to represent pieces and positions.

In order to achieve this, you can use Fen string. Fen string is basically board state information and it gives current pieces and their positions on board. So your board can have a method which returns current state of the board via Fen string then your UI layer can represent board as it wish. This is actually how current chess engines works. Chess engines are console application without GUI but we use them via external GUI. Chess engine communicate to GUI via fen strings and chess notation.

You are asking that what if i add a new piece ? It is not realistic that chess will introduce a new piece. That would be huge change in your domain. So follow YAGNI principle.

Freshblood
  • 1,517