13

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
  • 447
  • 3
  • 8
  • What is the purpose of having an own class for each type of chess piece? I reckon a piece object holds the position, the color and the type of a single piece. I'd probably have a Piece class and two enums (PieceColor, PieceType) for that purpose. – COME FROM May 22 '17 at 09:35
  • @COMEFROM well, obviously different types of piece have different behaviors, so there needs to be some custom code that distinguishes between say rooks and pawns. That said, I would generally rather have a standard piece class that handles all types and uses Strategy objects to customize behaviour. – Jules May 22 '17 at 15:51
  • @Jules and what would be the benefit of having a strategy for each piece over having a seperate class for every piece containing the behavior in itself? – lishaak May 23 '17 at 08:34
  • 2
    One clear benefit of separating the rules of the game from stateful objects that reperesent individual pieces is that it solves your problem immediately. I wouldn't generally mix the rule model with the state model at all when implementing a turn based board game. – COME FROM May 23 '17 at 11:04
  • 2
    If you don't want to separate the rules, then you could define the movement rules in the six PieceType objects (not classes!). Anyway, I think the best way to avoid the kind of problems you're facing is to separate concerns and to use inheritance only when it's really useful. – COME FROM May 23 '17 at 11:29
  • @lishaak Because using inheritance is more likely to fail when changes are made than using a separate object to vary behaviour (search for: fragile base class problem) and because separate objects can be composed and changed at run time, allowing more flexibility (e.g. consider what happens when a pawn is promoted to a queen: with a strategy, you can just call `setType(ChessPieceType.QUEEN)`, but with inheritance you need to create an entirely new `Piece` object). I generally find implementation inheritance to not be very useful, and much prefer techniques using aggregates of objects instead. – Jules May 25 '17 at 03:26
  • ok, it seems that I have asked in needlessly concrete way. What I am interested in is probably the general case of mapping business objects to their presenters. I have posted more general version of this question here https://softwareengineering.stackexchange.com/questions/349612/mapping-business-objects-to-their-presenters – lishaak May 25 '17 at 19:46
  • @lishaak In chess, pieces aren't really fixed number ? How can you add new pieces to chess ? – Freshblood May 25 '17 at 22:33
  • @Freshblood There are many extensions to chess rules sometimes called fairy pieces. As you can see here https://en.wikipedia.org/wiki/Fairy_chess_piece, there are many interesting possibilities even though you might not want to call the resulting game chess anymore. – lishaak May 27 '17 at 09:06

6 Answers6

10

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;

  @Override
  PiecePresenter(BoardPresenter board, Image image) {
    public void display(int rank, int file) {
      board.display(pieceImage, 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.

candied_orange
  • 102,279
  • 24
  • 197
  • 315
  • 2
    I think this is probably a good answer (+1), but I am not convinced that your solution is a good example of the Clean Architecture: the Rook entity now very directly holds a reference to a presenter. Isn't it this kind of coupling that the Clean Architecture is trying to prevent? And what your answer doesn't quite spell out: the mapping between entities and presenters is now handled by whoever instantiates the entity. This is probably more elegant than alternative solutions, but it is a third place to be modified when new entities are added – now, it's not a Visitor but a Factory. – amon May 21 '17 at 16:53
  • @amon, as I said, the use case layer is missing. Terry Pratchett called these kind of things "lies to children". I'm trying to not create an example that is overwhelming. If you think I need to be taken to school where it comes to Clean Architecture I invite you to take me to task [here](https://codereview.stackexchange.com/questions/148809/a-button-as-a-clean-architecture-plugin). – candied_orange May 21 '17 at 17:34
  • sorry, no, I don't want to school you, I just want to understand this architecture pattern better. I literally just came across the “clean architecture” and “hexagonal architecture” concepts less than a month ago. – amon May 21 '17 at 17:47
  • @amon in that case give it a good hard look and then take me to task. I'd love it if you did. I'm still figuring out parts of this out myself. At the moment I'm working on upgrading a menu driven python project to this style. A critical review of Clean Architecture can be found [here](https://dzone.com/articles/clean-architecture-is-screaming). – candied_orange May 21 '17 at 18:15
  • Hmmm, this answer seems to be the most interesting one. I have to think about it for a while and try to really understand it... – lishaak May 22 '17 at 09:03
  • Interfaces belong to software layers too, you declared an UI interface in Entity layer. – Basilevs May 23 '17 at 01:41
  • yes. It seems that not only does the business layer need to know that there is an UI layer (or at least some 'other' layer), from your code it seems that the piece itself has to be instantiated by the UI layer. What if I wanted to use several kinds of differnet presenters in different layers? For example if I had a command line which would require text presenters AND a UI layer which requires image presenters? – lishaak May 25 '17 at 20:30
  • 1
    @lishaak instantiation can happen in main. The inner layers don't know about outer layers. The outer layers only know about interfaces. – candied_orange May 25 '17 at 22:02
5

What about that:

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.

Timothy Truckle
  • 2,336
  • 9
  • 12
  • I see this as a very practical and down-to-earth solution for this particular scenario. 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. – lishaak May 21 '17 at 16:20
  • 3
    @lishaak: the general approach here is to provide enough *meta data* in your business objects so a general mapping to a resource or user interface elements can be done automatically. – Doc Brown May 22 '17 at 11:38
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.

Lasse Jacobs
  • 121
  • 2
  • OK, so let's say I have some `Piece* p`. How do I know that I have to create a `PawnView` to display it, and not a `RookView` or `KingView`? Or do I have to create an accompanying view or presenter immediately whenever I create a new Piece? That would basically be @CandiedOrange's solution with the dependencies inverted. In that case, the `PawnView` constructor could also take a `Pawn*`, not just a `Piece*`. – amon May 21 '17 at 16:57
  • Yes I am sorry, the PawnView constructor would take a Pawn*. And you don't necessarily have to create a PawnView and a Pawn at the same time. Suppose there is a game which can have a 100 Pawn's but only 10 can be visual at one time, in this case you can reuse pawnviews for multiple Pawns. – Lasse Jacobs May 21 '17 at 19:21
  • And I agree with @CandiedOrange's solution I just though I would share my 2 cents. – Lasse Jacobs May 21 '17 at 19:23
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,614
  • 2
  • 33
  • 63
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,515
  • 10
  • 23
  • Well first, the piece has to know the presentation layer is there and that it requires images. What if some presentation layer does not require images? Second, the piece has to be instantiated by the UI layer, since a piece cannot exist without a presenter. Imagine that the game runs on a server where there is no UI needed. Then you cannot instantiate a piece because you do not have UI. – lishaak May 27 '17 at 20:30
  • You can define representer as optional parameter as well. – Freshblood Jun 05 '17 at 11:41
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,515
  • 10
  • 23
  • Well, this solution is certainly functional and I appreciate the idea. However, it is very limited to chess. I used chess more as an example to illustrate the general problem (I may have made that clearer in the question). The solution you propose cannot be used in other domain and as you correctly say, there is no way to extend it with new business objects (pieces). There indeed are many ways to extend chess with more pieces.... – lishaak May 27 '17 at 09:59