Search code examples
javaoopcomposition

Object Oriented Programming - Two Way Composition Practice?


My question pertains to the aspect of "composition" in object oriented programming. I am programming in java, but this could apply to any language.

My issue is that of 2-way composition. I've heard composition implies a "has-a" relationship. Imagine you were building a checkers game, in my case I would have a "Board" class for generating the board, a board HAS squares, and squares can HAVE pieces (a Piece class), or be empty. Composition (based on my understanding) would say our board should have an array of square objects within it, and square objects should have a piece field within it.

public class Board {

    private List<Square>;

    ... (generating board, other functions/fields)

}

public class Square {

    private Piece piece;

    ... (other functions/fields)

}

public class Piece {

    ... (functions/fields)

}

This is how I understand composition. My issue is, yes, a board has squares and pieces, but doesn't it make sense that a piece also HAS a board? If you created a move method in the piece class, wouldn't you need to access the board and its squares? Something like this:

public class Piece {

    private Board board;

    ... (constructor/functions/fields)

    public void move(int x, int y) {

        if (this.board.getSquare(x, y).isEmpty())
            System.out.println("Piece moves to (x, y)");

        ... (etc.)
    }
}

Is this considered good design practice, or would there be a better way to implement this?


Solution

  • There are two issues here, really. Two-way composition - or, generally speaking, two-way association - is not itself an problem. It's an absolutely valid type of relation though somewhat impractical in implementation. Two-way association adds one more dependency, and every additional dependency increases coupling (check Low Coupling explained by Craig Larman as part of his GRASP principles).

    Still, two-way association sometimes is necessary to reflect the fact that two objects make sense only together. Yes, you can say that a piece has a board, but do we really say that in real life? Do we care about that? From domain point of view, that doesn't play any crucial role, at least when domain is just playing checks. Maybe in another imaginary domain, where we move pieces between many boards, that point of view could be crucial and we might need to model it with a two-way association. Now, having mentioned domains, I cannot avoid mentioning a classical book by Eric Evans on domain-driven design. Really, Eric does a great job explaining all the nuances of modeling domains with software. In particular, he also explains two-way (bi-directional) associations between objects (p. 83).

    Now, to the second issue. The necessity to hold a reference to the board in your case and so maintain a two-way association is caused by assigning responsibility to move pieces to wrong object. Moving pieces is an operation that may affect several pieces, and so one particular piece cannot handle that without breaking encapsulation. Instead, we should ask, who is that responsible object that could oversee all the pieces and manage them in a proper manner according to the domain rules (rules of playing checks)? Without adding any special objects, out of those we have already, that seems to be the board. Board looks a nice place to hold the logic that manipulates the content of the board (hence we respect the encapsulation).

    So, answering your actual question, it would be better to place move() method to the Board class, and automatically get rid of unnecessary two-way association. And as a follow-up on what I mentioned about assigning responsibilities, I would again mention GRASP (which really stands for General Responsibility Assignments Principles) - check it, I am sure it will help a lot with this and similar cases.