Una classe unificante complessa dovrebbe fare il calcolo?

0

Ho una grande applicazione in Java piena di classi indipendenti che sono unificate in una classe PlayerCharacter . La classe è destinata a contenere i dati di un personaggio per un gioco chiamato Burning Wheel e, di conseguenza, è insolitamente complessa. I dati nella classe devono avere calcoli controllati dall'interfaccia utente, ma devono essere eseguiti in modo particolare per ciascuno degli oggetti della classe.

Ci sono 19 oggetti nella classe PlayerCharacter . Ho pensato di ridurlo, ma sono abbastanza sicuro che non funzionerebbe in nessuna parte dell'applicazione così com'è.

private String name = ""; private String concept = "";
private String race = ""; private int age = -1;  

private ArrayList<CharacterLifepath> lifepaths = new ArrayList<>();

private StatSet stats = new StatSet();
private ArrayList<Affiliation> affiliations = new ArrayList<>();
private ArrayList<Contact> contacts = new ArrayList<>();
private ArrayList<Reputation> reputations = new ArrayList<>();
//10 more lines of declarations

Ho preso in considerazione questo problema da un po 'di tempo e ho considerato più approcci. Il problema sorge principalmente quando i dati vengono cancellati - ad esempio, poiché praticamente tutto il resto (ma non alcune parti!) Dipende da Lifepath, quando un lifepath viene eliminato, quasi tutto il resto deve essere ricalcolato. Tuttavia, se un'abilità viene eliminata, solo alcune cose devono essere ricalcolate.

Inoltre, l'applicazione deve tracciare determinati valori da qualche parte; punti abilità, punti tratti, ecc. per garantire che l'utente non superi inavvertitamente tali valori.

Quindi la mia domanda è generalmente la seguente: dove dovrebbe andare tutto? Cosa rende questo più semplice? Ci sono un paio di opzioni:

  • Calcoli totali dei punti di posizionamento nella classe PlayerCharacter (ma come si genera un avviso?)
  • Gestisci tutti i calcoli al di fuori della classe PlayerCharacter, e usa semplicemente PlayerCharacter come contenitore per tutte le informazioni del personaggio
  • Inserisci tutti i calcoli nella classe PlayerCharacter; dopo che ogni elemento è cambiato, ricalcolare l'intero personaggio. Quindi, se ci sono problemi derivanti dall'eliminazione, riporta gli avvisi all'interfaccia utente.

Sono leggermente sopraffatto dallo scopo di questa particolare classe - mentre tutto quello che ho fatto finora è stato facilmente suddiviso in piccoli pezzi gestibili, questa bestia sembra resistere all'essere domata. Se c'è un approccio migliore a questo, sono tutto orecchie! Ma al momento, sono un po 'confuso, e progredire senza meta probabilmente probabilmente non mi porterà da nessuna parte. Qualsiasi consiglio è apprezzato.

Mi scuso se questo non è chiaro - sono certo che mi manca il vocabolario del software per comunicare correttamente le mie idee. Mi piacerebbe migliorare questa domanda - qualsiasi aiuto qui è apprezzato!

    
posta Zyerah 21.05.2013 - 07:21
fonte

3 risposte

1

Interfaccia separata dall'implementazione. "PlayerCharacter" dovrebbe essere un'interfaccia con metodi come "getStats", "getContacts", "getReputation" ecc. Ciò ti dà la libertà di giocare con l'implementazione senza dover cambiare il codice che usa questa interfaccia.

L'implementazione può scegliere di memorizzare i valori nella cache e ricalcolarli se necessario. Ad esempio un metodo "getStats" restituirebbe il valore della variabile "stats"; e se la variabile è vuota, dovrebbe essere ricalcolata per prima. Metodi come "removeLifepath" possono rimuovere tutti i valori relativi a Lifepath memorizzati nella cache.

I metodi per il ricalcolo effettivo potrebbero essere inseriti in un'altra classe, se necessario. Quindi avresti: A) l'interfaccia, B) una o più classi che eseguono il calcolo, e C) un'implementazione che mantiene i valori memorizzati nella cache e chiama il ricalcolo quando necessario.

Schizzo rapido:

interface PlayerCharacter {

    List<Lifepath> getLifepaths();

    void addLifepath(Lifepath lifepath);

    void removeLifepath(Lifepath lifepath);

    Skills getSkills();

}

class PlayerCharacterImpl {

    List<Lifepath> lifepaths = new ArrayList<>();
    Skills cachedSkills = null;

    public List<Lifepath> getLifepaths() {
        return Collections.unmodifiableList(lifepaths);
    }

    public void addLifepath(Lifepath lifepath) {
        lifepaths.add(lifepath);
        cachedSkills = null;
    }

    public void removeLifepath(Lifepath lifepath) {
        lifepaths.remove(lifepath);
        cachedSkills = null;
    }

    // thread-unsafe
    public Skills getSkills() {
        if (null == skills) {
            skills = PlayerCharacterCalc.calculateSkills(lifepaths);
        }
        return skills
    }

}

class PlayerCharacterCalc {

    static Skills calculateSkills(List<Lifepath> lifepaths) {
        Skills skills = new Skills();
        for (Lifepath lifepath : lifepaths) {
            skills.apply(lifepath);
        }
        return skills;
    }

}
    
risposta data 21.05.2013 - 14:46
fonte
1

Questa sarà una risposta parziale poiché non sono un utente regolare di Java (più di un ragazzo C ++). Ecco alcune idee che potrebbero guidarti:

1) Se un attributo può essere modificato da più fonti (uno Stat calcolato con un valore base, bonus / malus da un'abilità e bonus da più lifepath), forse non dovresti salvarlo come un singolo valore, ma come un associazione di valore; e potrebbe essere usato per limitare il valore totale se bacchetta per essere sicuro che non lascerà dei limiti. Ad esempio, qualcosa del genere:

public class StatModifier {
  public String source; // the 'editing' source (ex : a lifepath)
  public String target; // the edited Stat (ex : strength)
  public int modifier;
}

public class Stat {
  private int base;
  private ArrayList<StatModifier> modifiers;
  private int computedValue;
  private int actualValue;
  private bool computed;

  public function addModifier(modifier) { // removeModifier() is basically the same
    modifiers.add(modifier);
    computed = false;
  }
  public function getValue() {
    if (!computed) {
      computedValue = base;
      // TODO : here compute value (ex : sum modifiers)

      actualValue = Math.min(computedValue, 100); // Max stat value : 100
      computed = true;
    }
    return actualValue;
  }
}

Se si utilizza questo metodo, le modifiche di Lifepath sono un elenco di modificatori e quando si aggiunge / rimuove un lifepath, è sufficiente aggiungere / rimuovere i modificatori associati. Quei modificatori sarebbero logicamente presenti nell'istanza Lifepath e la classe PlayerCharacter dovrà solo recuperare questo elenco ed elaborarlo.

2) se devi limitare il valore totale di un gruppo (ad esempio, un personaggio non può avere più di 50 punti abilità in tutte le abilità), potresti usare una classe SkillSet (per esempio) che funzionerebbe come una lista di abilità e un validatore. In questo modo, la classe PlayerCharacter deve solo richiedere il metodo validate () di tutte queste classi per essere sicuro che il carattere sia valido e le regole di convalida effettive saranno gestite impostate per set.

3) mantenere un elenco delle modifiche apportate dall'ultima convalida. In questo modo, se rilevi che l'utente non è valido, puoi eseguire il rollback.

4) se usi '1)', una fase di validazione potrebbe anche essere quella di verificare che le fonti di ogni modificatore (ancora) esista.

    
risposta data 21.05.2013 - 15:21
fonte
1

Isola e amp; Incapsula la complessità

Tirare i calcoli nelle proprie classi di "funzione". La loro struttura e le relazioni dipendono molto dal tuo progetto esistente.

Forse trasforma affiliations , contacts , ecc. in classi. Questo rende PlayerCharacter più utilizzabile per classi di "funzione".

Con PlayerCharacter ora un insieme di altre classi e proprietà individuali, come opportuno, puoi facilmente creare classi di "dati". Queste classi di dati sono intese come personalizzazioni per le classi funzionali. Il codice di classe della funzione avrà l'aspetto e l'atmosfera di gestire una classe coerente, non un miscuglio di PlayerCharacter e altre classi.

Scrub your Design

Dovrai riesaminare per assicurarti di avere funzionalità fondamentali nelle classi giuste. Ad esempio, ogni classe "data" può avere la sua funzione "cancella" che fa cose molto specifiche e minimali. Una classe "funzione", allo stesso modo, orchestrerà tutte le classi "dati" nel processo di cancellazione generale e farà cose "delete-y" che sono di un contesto interclassico.

Ottieni una visualizzazione di alto livello dei modelli di progettazione

Scopri alcune nozioni di base sui vari modelli. Questo ti darà un'idea di come potresti affrontare il problema. Non sudare i diagrammi delle classi, basta avere le idee e le motivazioni dietro di loro. Non sto dicendo che dovresti implementare esplicitamente un particolare schema o qualsiasi modello. Stai solo cercando ispirazione.

Peso vivo - Stato condiviso.

Visitatore - Esecuzione della stessa operazione su una raccolta di tipi diversi

Metodo modello - definisce un algoritmo in una classe base usando operazioni astratte che sottoclassi l'override per fornire un comportamento concreto.

Adattatore -     Converti l'interfaccia di una classe in un'altra interfaccia che i clienti si aspettano.     Adapter consente alle classi di funzionare insieme, altrimenti non potrebbe a causa di interfacce incompatibili.

Façade - Fornisce un'interfaccia unificata a un insieme di interfacce in un sottosistema. Façade definisce un'interfaccia di livello superiore che semplifica l'utilizzo del sottosistema.

    
risposta data 21.06.2013 - 22:31
fonte

Leggi altre domande sui tag