TL;DR;
None. Have a central authorithy do the book-keeping but inject it as a delegate to both players and matches. It would seem as if Match
had a list of Players
and Player had a reference to its match. We cannot trust players or matches enforcing the 1P-1M rule, do we?
Long answer
I designed a solution which is a class called MatchManager
who is responsible for registering and unregistering players into matches enforcing the 1P-1M rule. You can also ask it for a list of players for a given match and what match a given player is registered it.
The interesting part is that MatchManager
is a singleton and both players and matches are injected the manager as a delegate to their methods.
That way every operation players or matches do are garanteed to be centralized ans synced.
Obviously if you want this solution to be thread safe you should use thread safe versions of the collection classes used.
Show me the code
==> Match.java <==
package matches;
import java.util.ArrayList;
import java.util.List;
public class Match {
private MatchManager matchManager;
public Match(MatchManager matchManager){
this.matchManager = matchManager;
}
public void addPlayer(Player player) throws PlayerAlreadyInAnotherMatchExcepction {
this.matchManager.registerPlayerInMatch(this, player);
}
public void removePlayer(Player player) throws PlayerNotInMatchException {
this.matchManager.unregisterPlayerFromMatch(this, player);
}
public List<Player> getMatchPlayers() throws MatchNotRegisteredException{
return this.matchManager.getMatchPlayers(this);
}
}
==> Player.java <==
package matches;
public class Player {
private MatchManager matchManager;
public Player(MatchManager matchManager){
this.matchManager = matchManager;
}
public Match getMatch() {
return this.matchManager.getMatch(this);
}
}
==> MatchManager.java <==
package matches;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public final class MatchManager {
private static MatchManager instance;
private MatchManager(){}
public static MatchManager getInstance(){
if (instance == null){
instance = new MatchManager();
}
return instance;
}
Map<Player,Match> regPlayersToMatches = new HashMap<Player,Match>();
Map<Match,List<Player>> regMatchesToPlayers = new HashMap<Match,List<Player>>();
public void registerPlayerInMatch(Match match, Player player)
throws PlayerAlreadyInAnotherMatchExcepction {
Match m = this.regPlayersToMatches.get(player);
List<Player> p;
if ( m == null ){
this.regPlayersToMatches.put(player, match);
p = new ArrayList<Player>();
p.add(player);
this.regMatchesToPlayers.put(match, p);
} else {
throw new PlayerAlreadyInAnotherMatchExcepction();
}
}
public void unregisterPlayerFromMatch(Match match, Player player)
throws PlayerNotInMatchException {
List<Player> p = this.regMatchesToPlayers.get(match);
if (p==null){
throw new PlayerNotInMatchException();
}
if (p.contains(player)){
throw new PlayerNotInMatchException();
}
this.regPlayersToMatches.remove(player);
this.regMatchesToPlayers.remove(match);
}
public boolean isPlayerInMatch(Match match, Player player){
List<Player> p = this.regMatchesToPlayers.get(match);
if (p==null){
return false;
}
return (p.contains(player));
}
public List<Player> getMatchPlayers(Match match) throws MatchNotRegisteredException{
List<Player> p = this.regMatchesToPlayers.get(match);
if (p==null){
throw new MatchNotRegisteredException();
}
return new ArrayList<Player>(p);
}
public Match getMatch(Player player) {
return this.regPlayersToMatches.get(player);
}
}
==> PlayerAlreadyInAnotherMatchExcepction.java <==
package matches;
public class PlayerAlreadyInAnotherMatchExcepction extends Exception {
}
==> PlayerNotInMatchException.java <==
package matches;
public class PlayerNotInMatchException extends Exception {
}
==> MatchNotRegisteredException.java <==
package matches;
public class MatchNotRegisteredException extends Exception {
}
Note: I didn't use interfaces to keep the answer shorter. If you want to comply with the DIP, write interfaces (or abstract classes) to keep dependencies at bay.
Player -> Match
ref. should exist based on how problematic constantly iterating is assumed be. I don't disagree w/ the selected answer, code must be written! Just make sure implementation concerns do not overly influence design in howPlayer
is used in other contexts.