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.
CodePudding user response:
the distributeCards method of Game class needs to modify the cards attribute of a Player object.
One class should never modify the attributes of another class. This breaks the encapsulation principle.
Instead, the Player
object should declare a public
method which the Game
object can call. For example, you might add public giveCard(Card card)
to the Player
class to simulate handing a single card to a player. Then the Player
class decides what to do with the Card
object. For example, it could insert it into the cards
list.
Note that in this interaction, the Game
object has no knowledge of the cards
list. It only knows that Player
has a method named giveCard()
which it can call.
CodePudding user response:
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 theGame
for the list of cards of a specificPlayer
.Maybe the
Game
is responsible for providing thePlayer
objects, so it has access to its internal state.Player
could be an interface thatGame
is responsible for providing with a private inner class. OrPlayer
is an immutable class, constructed by theGame
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 ofPlayer
objects. TheGame
is responsible for the list of cards in thePlayer
object.A
giveCard(Card)
method just like Code-Apprentice writes. This means that you rethink your constraints and allow everyone to modify thePlayer
. Additionally you can even make a convenience methodgiveCards(List<Card>)
where thePlayer
copies the card list argument in its internal state. This way no outsider is able to directly modify thePlayer
'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 anIllegalStateException
if thePlayer
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. theGame
shuffles the cards and gives the shuffled list to the firstPlayer
; thePlayer
picks some cards, moves them to its internal state and removes them from the shuffled list;Game
gives the remaining cards to the nextPlayer
. 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 aPlayer
) etc are immutable value objects holding the current state of a game. (Hint: check out Immutables.) Another class, sayGameEngine
, 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 :)