Se una partita tiene una lista di giocatori o dovrebbe un giocatore mantenere un riferimento alla sua partita?

4

Nel gioco a cui sto lavorando c'è un oggetto Match e un oggetto Player.

Il gioco è diviso in diverse partite (fondamentalmente una lobby) . Ogni giocatore deve partecipare a una partita, ma non può partecipare a più partite contemporaneamente.

La corrispondenza contiene il metodo List<Player> getPlayers() (elenco di giocatori connessi alla partita) . Il giocatore contiene il metodo Match getMatch() (istanza di corrispondenza con cui il giocatore è connesso) .

Ho faticato su dove sono le informazioni che indicano quale Giocatore è in quale Partita.

  • Se la partita mantiene un elenco dei giocatori connessi a quella partita, il metodo Player.getMatch() dovrebbe scorrere un metodo MatchManager.getMatches() per provare a trovare la corrispondenza che contiene l'istanza Player.

  • Se il giocatore mantiene un riferimento alla partita a cui è collegato, il metodo Match.getPlayers() dovrebbe eseguire il ciclo di un metodo PlayerManager.getPlayers() per trovare ogni singolo giocatore connesso a tale corrispondenza.

  • Se l'oggetto Match mantiene un elenco di giocatori connessi e l'oggetto Player mantiene un riferimento all'oggetto Match a cui è connesso, né Match.getPlayers()Player.getMatch() devono scorrere un elenco globale ma possono solo restituire il loro riferimento. Tuttavia, ritengo che questo sia molto incline agli errori in quanto potrebbe essere facile che i riferimenti vengano de-sincronizzati e non coincidenti con l'asserzione "Il giocatore deve trovarsi esattamente in una partita".

Non sono sicuro quale approccio sarebbe appropriato. Sembra che sto facendo qualcosa di sbagliato in tutto il mio layout di codice che questo diventa un problema e che l'intero pattern di Manager è un po 'anti-pattern, ma non sono sicuro di come impaginare il mio codice.

Qualche suggerimento o idea su come dovrei pensare a questo (e simili domande sulla disposizione del codice) sarebbe molto apprezzato.

Non sono sicuro se questo è appropriato in softwareengineering, ma non sono sicuro di dove sia il posto migliore per chiedere le pratiche di codifica.

    
posta Vapid Linus 06.11.2016 - 02:14
fonte

5 risposte

2

Quello che stai descrivendo è una relazione bidirezionale a molte.

Se si dispone di un database, è possibile aggirare questo problema semplicemente modellando questo e consultandolo con una query. Se, tuttavia, vuoi mantenerlo con una struttura dati, stai andando in loop o mantieni una struttura dati ombra che può diventare incoerente se non mantenuta.

Sono favorevole al fatto che Player mantenga un riferimento a Match semplicemente perché applicare l'asserzione "Il giocatore deve trovarsi esattamente in una corrispondenza" diventa un semplice test per null.

Rifiutare il loop potrebbe essere un'ottimizzazione prematura. Verifica quanti giocatori e partite devono essere coinvolti prima di trovare la reattività intollerabile.

Se decidi di rifiutare il loop, siamo bloccati con Player mantenendo un riferimento e Match mantenendo un elenco shadow. Attenzione, questo è un riferimento circolare. Stai attento a come attraversi.

Come mantenere la lista shadow coerente? Aggiornalo quando, e solo quando, il riferimento viene aggiornato. Mantieni Match es aggiorna il metodo accessibile solo a Player se puoi. Quindi qualcosa del genere si occupa di mantenere la coerenza:

class Player {
    ...
    void setMatch(Match match){
        this.match.removePlayer(this);
        this.match = match;         
        this.match.addPlayer(this);
    }
}

Fintanto che Player mantiene this.match da null e nient'altro ha problemi con add / remove player, questo dovrebbe mantenere le cose coerenti. Ma alcune asserzioni ben posizionate possono aiutarti a garantirlo.

    
risposta data 06.11.2016 - 07:09
fonte
4

In astratto, il vincolo è sul giocatore, quindi avrei la partita tenere un elenco di giocatori, e rendere i giocatori responsabili per l'adesione alla partita. La partita deve semplicemente conoscere i giocatori uniti.

Se, a un certo punto, è possibile pianificare le partite future, ciò consentirebbe ai giocatori di controllare ancora la partita una volta in un vincolo di tempo, e la logica di join MatchPlay non dovrebbe cambiare.

Modifica:

Per quanto riguarda l'avere entrambe le partite e il giocatore ha riferimenti, questo può essere il più utile, specialmente se ci sono centinaia o migliaia di giocatori in una partita, e trovare un giocatore specifico è un'esigenza comune per l'elaborazione della corrispondenza.

Per espandere un po 'di più. Non sono sicuro di cosa, se non altro persistesse. Se c'è persistenza, manterrei una lista di giocatori per ogni partita, il giocatore (supponendo che sia un oggetto durevole) probabilmente manterrà una lista di oggetti MatchPlay. MatchPlay sarebbe un riferimento di corrispondenza con un oggetto risultati / stato (riferimento permanente). Quindi sarai in grado di visualizzare analisi del giocatore, monitorare lo stato, mostrare partite imminenti e storiche per il giocatore.

Inoltre, se partecipare a una partita aveva alcune qualifiche basate su MATCH (min / max rating, token pagato, invito), quindi il processo di join avviato dal giocatore sarebbe più un join RICHIESTA. I vincoli del giocatore possono filtrare le corrispondenze join-able, mentre il vincolo MATCH influisce sulla richiesta di join.

E se il concetto di risultato della partita è cotto, lasciare il riferimento alla partita nel giocatore non dovrebbe essere un problema.

Ovviamente, questo potrebbe essere sovrascritto per un gioco arcade coin-op non in rete da due a quattro giocatori, hah.

    
risposta data 06.11.2016 - 16:49
fonte
2

TL; DR;

Nessuno. Avere un'autorità centrale che faccia la contabilità, ma iniettarla come delegato sia per i giocatori che per le partite. Sembra che Match abbia un elenco di Players e Player abbia avuto un riferimento alla sua corrispondenza. Non possiamo fidarci di giocatori o partite che impongono la regola 1P-1M, vero?

Risposta lunga

Ho progettato una soluzione che è una classe chiamata MatchManager che è responsabile della registrazione e annullamento della registrazione dei giocatori in partite che applicano la regola 1P-1M. Puoi anche chiederlo per un elenco di giocatori per una determinata partita e che cosa corrisponde a un dato giocatore è registrato.

La parte interessante è che MatchManager è un singleton e ad entrambi i giocatori e le partite viene iniettato il gestore come delegato ai loro metodi.

In questo modo, ogni operazione eseguita da giocatori o fiammiferi è garantita per essere centralizzata e sincronizzata.

Ovviamente se vuoi che questa soluzione sia sicura per i thread devi usare versioni thread safe delle classi di raccolta utilizzate.

Mostrami il codice

== > 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 {

}

Nota: non ho usato le interfacce per mantenere la risposta più breve. Se si desidera rispettare il DIP, scrivere interfacce (o classi astratte) per tenere a bada le dipendenze.

    
risposta data 07.11.2016 - 16:46
fonte
1

Troverò che la tua vera domanda è "come posso decidere quando fare una relazione tra due oggetti bidirezionali, e se scelgo di renderla unidirezionale, come faccio decidere da che parte? "

La risposta a questa domanda sta nei requisiti chiaramente definiti. Se i tuoi requisiti non sono chiari, stai perdendo tempo a cercare di compensare entrambi i casi, torna al modello e assicurati che sia chiaro.

In quasi tutti i casi, vuoi che le relazioni siano unidirezionali poiché questo è il più facile da codificare e comprendere. Se i requisiti richiedono realmente una relazione a due vie, potrebbe essere meglio estrapolare la relazione stessa come un terzo oggetto e assegnare ad ogni oggetto una relazione a senso unico.

In alcuni casi, scopri che in realtà non hai bisogno di una relazione modellata dagli oggetti. Penso che questo potrebbe essere adatto al tuo caso. Quello che stai cercando di dare dando una relazione a partite e giocatori è la query per informazioni su come le partite e i giocatori si allineano. Se estrai questo servizio come un servizio che accetta un ID giocatore o un ID partita e ti fornisce semplicemente le informazioni che stai richiedendo, perché giocatori e partite hanno bisogno di conoscersi?

Bene, quindi sai come programmare e sai come implementare tutti gli scenari possibili, e sai che ci sono una varietà di risposte alle domande poste, ma la tua vera domanda riguarda come prendere la decisione in ogni scenario dato . Qual è il modo migliore di dare il progetto su cui stai lavorando? La semplice risposta è che devi conoscere il tuo modello e le tue esigenze, e se sei perplesso su un problema come questo, è perché il tuo modello o le tue esigenze non sono chiare come potrebbero essere. Torna indietro e trova il modello.

Se vuoi saperne di più ti consiglio Domain Driven Design di Eric Evans. Se non l'hai letto, leggerlo. Potrebbe non affrontare esattamente la tua situazione, ma i principi che ne prendi ti serviranno tecnologicamente per sempre. Se l'hai letto, rileggilo, perché affronta alcuni modi di pensare che colpiscono il cuore della tua vera preoccupazione:

I am unsure which approach would be appropriate. It feels like I'm doing something else wrong with my entire code layout that this becomes a problem

    
risposta data 07.11.2016 - 17:25
fonte
0

Secondo me, la partita dovrebbe tenere una lista di giocatori. In questo modo, puoi avere cronologia e statistiche in base a quale giocatore ha giocato la partita.

Ovviamente, devi avere una convalida per garantire che un giocatore non possa giocare due partite contemporaneamente, ma quel tipo di logica dovrebbe essere collocato in una sorta di classe di arbitro che ha informazioni sia sui giocatori che sulle partite (es. ).

    
risposta data 07.11.2016 - 14:15
fonte