I have two classes Player
and Game
public class Player {
private List<String> cards;
public Player(){
cards = new ArrayList<String>();
}
}
public class Game {
List<Player> players;
public Game(List<Player> players){
this.players = new ArrayList<Player>();
for(Player player: players){
this.players.add(player);
}
}
private void distributeCards(){
...
}
public void start(){
distributeCards()
...
}
}
the distributeCards
method of Game
class needs to modify the cards attribute of a Player object. If I declare a public or protected method in the Player class for that, other classes in the same package will have the privilege as well but I don't want that. In this situation what can I do?
Let me know if I'm breaking any rules of design principles or design patterns.
Things I would consider, given the assumption that the Player
must expose the list of cards somehow:
Maybe, in your design & implementation, the cards are not a property of the player but of the game; e.g.:
public class Game {
List<Player> players;
Map<Player, List<String>> cards;
This would work if only the Game
wants to know the list of cards or if the rest of the system can ask the Game
for the list of cards of a specific Player
.
Maybe the Game
is responsible for providing the Player
objects, so it has access to its internal state. Player
could be an interface that Game
is responsible for providing with a private inner class. Or Player
is an immutable class, constructed by the Game
with a list of cards.
Another way to approach this option is to think e.g. that a "Player" is actually a user within a game. So the Game
takes the list of users that will be playing and transforms it to a list of Player
objects. The Game
is responsible for the list of cards in the Player
object.
A giveCard(Card)
method just like Code-Apprentice writes. This means that you rethink your constraints and allow everyone to modify the Player
. Additionally you can even make a convenience method giveCards(List<Card>)
where the Player
copies the card list argument in its internal state. This way no outsider is able to directly modify the Player
's internal state.
As an amendment to this option, you could add validation logic to the giveCards(List<Card>)
method, so that e.g. it throws an IllegalStateException
if the Player
already has cards. The drawback here is that this behavior is not obvious from the design of the class.
You could move the logic of card distribution to the Player
. So, e.g. the Game
shuffles the cards and gives the shuffled list to the first Player
; the Player
picks some cards, moves them to its internal state and removes them from the shuffled list; Game
gives the remaining cards to the next Player
. This is not always appropriate, you judge according to your design.
In my own experiments with card games the design was that Game
, Player
, Card
, Hand
(the list of cards of a Player
) etc are immutable value objects holding the current state of a game. (Hint: check out Immutables.) Another class, say GameEngine
, is responsible for taking a state and returning a new immutable state in response to a user or game action. Game
is the root state object, everything else is reachable through it. E.g.:
public class GameEngine {
public Game distributeCards(Game game) {
...
}
}
You can add the business logic methods to the Game
class if you want, the gist here is the immutability and that each action returns a new state. Think Redux for Java :)