Sistema globale di base per due sottocomponenti personalizzabili

-1

La seguente spiegazione è piuttosto dettagliata, ma penso che se avessi semplificato il problema non sarebbe stata data una risposta adeguata.

Sto lavorando a un sistema di scripting per un gioco (per gli utenti finali piuttosto che per l'uso interno degli sviluppatori). Il gioco ha sia modalità singleplayer che multiplayer. Esiste una classe Script di base, ereditata da MultiplayerScript e SingleplayerScript (in modo nativo, gli script multiplayer avranno determinate funzioni / API per più giocatori - pensi agli indirizzi IP dei giocatori, ecc.)

Ho anche il codice per caricare e gestire gli script; il codice è largamente condiviso tra le modalità MP e SP (ad esempio, l'analisi del comando load emesso dall'utente), ma ci sono alcune piccole differenze: la directory in cui il codice deve cercare gli script, il modo in cui uno script viene istanziato ( a seconda della modalità, desidero istanziare SingleplayerScript o MultiplayerScript ). Mi viene in mente il concetto di ereditarietà - forse una base ScriptManager con tutto il codice comune più alcuni metodi astratti da sovrascrivere da sottoclassi specifiche della modalità che implementano funzionalità specifiche. Ad esempio:

void ScriptManager::load(const std::string& command)
{
    const auto name = getNameFromCommand(command);
    // This is a virtual method that returns an instantiated script object
    const std::shared_ptr<Script> script = instantiateFromName(name);
    script->initialize();
    scripts.push(script);
}

Il problema è che il concetto ScriptManager è intrinsecamente globale; ad esempio, manterrà un elenco di tutti gli script attualmente caricati (come puntatori alla classe Script di base). Qualche altro codice comune potrebbe voler accedere a queste informazioni. Tuttavia, se faccio semplicemente una sottoclasse di ScriptManager per implementare questa specifica funzionalità, dovrò istanziare manualmente MultiplayerScriptManager da qualche parte nel codice multiplayer, e allo stesso modo per la modalità singleplayer. Ciò significa che il codice condiviso non ha più accesso all'istanza del gestore di script.

Ora, ancora una volta, la prima cosa che mi viene in mente è lo schema singleton, ma non credo che questo si mescoli bene con l'ereditarietà; mentre è possibile , non sono convinto che sia la soluzione più elegante di sempre e visto che non è così esotica problema, mi sento come ci deve essere un modo migliore.

Quanto sopra è semplicemente ciò a cui sono stato in grado di pensare (e respinto in quanto insoddisfacente); la domanda è come risolvere questo problema in modo elegante, non come risolverlo collegando singoletti ed eredità (anche se questo risulta essere il modo migliore, così sia).

La mia piattaforma è C ++ 14, anche se credo che le soluzioni si estenderanno probabilmente anche a C # o Java ecc.

    
posta edcruz 14.09.2017 - 22:35
fonte

4 risposte

0

Non è completamente chiaro dalla domanda, ma suppongo che sia necessario combinare script singoli e multiplayer.

Se è così, allora ti consiglierei di avere uno, globale ScriptManager ma, piuttosto che la sottoclasse instantiateFromName , renderlo un metodo di fabbrica.

In questo schema, instantiateFromName deciderebbe quale tipo di script istanziare, presumibilmente basato su qualcosa sul nome o sul comando, e restituire quell'oggetto.

ScriptManager funzionerebbe sempre solo sul supertipo astratto e in quanto tale non avrebbe bisogno di sottoclassi. Potrebbe quindi essere un singleton globale o comunque desideri gestire lo stato globale.

    
risposta data 15.09.2017 - 11:52
fonte
0

CRTP mi vengono in mente, ma sarebbero necessari ulteriori dettagli per determinare se questo è applicabile. Inoltre, sarebbe difficile prevedere se alla fine si incontreranno casi d'uso che il CRTP non può risolvere.

CRTP consente fondamentalmente di scrivere il codice per il corpo del metodo, accedendo a campi che non esistono affatto nella classe base, ma sarebbero dichiarati dalle rispettive classi figlio concrete che ereditano dalla classe CRTP. È come se un genitore avesse accesso al conto bancario di un bambino.

Un'alternativa più semplice consiste nell'avere metodi virtuali che forniscono l'accesso ai campi sottostanti in ogni classe figlia.

Si noti che questi metodi virtuali non possono essere chiamati mentre il costruttore o il distruttore della classe base è in esecuzione.

Per esplorare queste opzioni, dovrai scrivere un codice prototipo che viola deliberatamente la regola "Tell, Do not Ask". Prova a prototipare le funzioni condivise come se non fossero funzioni membro. Invece, questa funzione non membro utilizzerà getter e setter per accedere e manipolare i dati dell'istanza secondaria.

Può darsi che la possibilità di condividere il codice tra le due classi figlio sia un'illusione (qualcosa che sembra possibile e logico, ma la realtà potrebbe dirti che il codice diventerà così diverso che la condivisione non è possibile). In questo caso potresti dover tornare a una classe base astratta, che definisce i punti di interazione senza fornire implementazioni di funzioni (corpo del metodo).

L'istanziazione è la parte facile - basta istanziare le due istanze concrete, a livello di applicazione (può essere un membro statico della classe concreta o esterna).

Crea un'istanza di oggetti SP e MP anche se il gioco può essere in una sola modalità alla volta). Delay inizializza alcune cose se possibile. In un'applicazione complessa come un gioco, l'inizializzazione a due fasi / multifase può essere utile.

    
risposta data 15.10.2017 - 23:29
fonte
0

Esiste un caso d'uso ragionevole in cui vorresti introdurre una terza modalità, oltre al single-player e al multi-player? In caso contrario, non è necessario estensibilità.

Dalla tua descrizione sembra che le differenze di comportamento dello scripting tra modalità single e multi-player siano minime. In tal caso, l'opzione migliore è non utilizzare l'ereditarietà, ma invece è sufficiente utilizzare una variabile membro enum per tenere traccia della modalità. Dove il comportamento differisce tra le modalità, basta usare if / else per differenziare.

    
risposta data 14.01.2018 - 14:34
fonte
-1

Non usare il modello singleton. Questo modello inserisce semplicemente il singolo campo statico globale nella classe, che non è ciò che si desidera. Puoi ancora utilizzare un campo statico globale, mantienilo a livello di applicazione, non nelle classi di gestione.

    
risposta data 15.09.2017 - 01:22
fonte

Leggi altre domande sui tag