Oggetto tagliato a pezzi per motivi tecnici: dovrebbe essere modellato come 1 o 2 oggetti?

2

Sto facendo un gioco Ho un oggetto Replay che rappresenta tutti gli input fatti da un giocatore per finire un livello. Sembra che:

class Replay {
    int replayId;
    int playerId;
    int levelId;
    int length;
    List<Input> inputs;
}

Voglio essere in grado di cercare e accedere ai replay molto velocemente, quindi li sto salvando in un database. Tuttavia, mi è stato detto non dovrei usare il database per archiviare file binari di grandi dimensioni. Quindi sto memorizzando il replayId (chiave primaria), il playerId , il levelId e il length sul database e l'oggetto completo è serializzato in un file chiamato usando la chiave primaria. Tutto ciò va bene e posso avvolgere completamente quelle cose hacky in un oggetto che astrarrà il caricamento e il salvataggio di oggetti Replay .

Tuttavia, progettando quell'oggetto, verrò per caso in cui non ho bisogno di caricare completamente Replay , ma ho semplicemente bisogno dell'id del giocatore che l'ha fatto o dell'ora. Quindi ho finito per creare metodi che assomigliano un po 'a questo:

interface ReplayService {
    //query to Database and then loading from disk
    Replay getWorldRecord(int levelId) throws LevelNotFoundException, RecordNotSetException; 

    //query from database only and return length
    int getWorldRecordTime(int levelId) throws LevelNotFoundException, RecordNotSetException;

    //query from database only and return playerId
    int getWorldRecordHolder(int levelId) throws LevelNotFoundException, RecordNotSetException;
}

Questa è la soluzione più pulita che ho trovato, ma il problema è che il tempo e il titolare del record vengono trovati insieme a una singola query SQL. Ciò rende l'implementazione molto stupida per i 2 metodi che restituiscono un singolo valore della stessa query. Quando un utente che dovrà visualizzare l'ora e il titolare, pagherà il prezzo di 2 query anziché 1 che restituisce comunque tutti i dati dal database. Quindi avrei bisogno di un modo per raggruppare quei 2 (o più) campi che voglio recuperare contemporaneamente.

interface ReplayService {
    //query to Database and then loading from disk
    Replay getWorldRecord(int levelId) throws LevelNotFoundException, RecordNotSetException; 

    //query from database only and return all infos without the data
    SomeKindOfType getWorldRecordInfo(int levelId) throws LevelNotFoundException, RecordNotSetException;
}

Ho cercato Pair oggetti in Java e ho incontrato persone che spiegavano perché è cattiva pratica e qualsiasi oggetto dovrebbe avere un nome significativo. Sono d'accordo con questo come anche se non voglio pagare per 2 query, ma non voglio nemmeno offuscare il mio codice.

Anch'io odio davvero Info oggetti come ReplayInfo poiché un oggetto Replay valido dovrebbe contenere le sue informazioni e la parola Info non aggiunge nulla in termini di significato. È un po 'lo stesso per Metadata . Ma non sono sicuro di avere altre opzioni migliori. Devo creare un nuovo tipo per memorizzare la parte Database dell'oggetto? Spero di no dato che la cosa di Database / File hacking è parte dell'implementazione e non del design. Mi piacerebbe essere in grado di realizzare un'implementazione diversa che memorizza i dati in modo completamente diverso senza che l'API si senta a disagio.

Qualche idea su come progettare un sistema utilizzando oggetti incompleti per motivi tecnici, ma vuoi comunque rimanere pulito e facile da capire? Devo solo schivare l'oggetto Info / Metadata con un generico Pair o Tuple ?

    
posta Winter 09.08.2017 - 01:16
fonte

2 risposte

1

Potrei non capire ancora i tuoi problemi, ma sono d'accordo con il desiderio di evitare le lezioni di FooInfo. Funzionerebbe per te se:

  1. Al momento della costruzione, Replay legge tutti i campi "infoish" dal database, ma lascia gli input nulli o vuoti. Utilizzare un'astrazione / libreria DB standard per eseguire SELECT in modo che questa parte del codice non cambierà in modo improbabile. Se aggiungi un nuovo campo "data" o cambi playerID in una stringa, dovrebbe essere semplice aggiornare Replay .

  2. Solo quando il client richiede gli input, li leggi dal file. Questa parte del codice è la parte "personalizzata" e la tua decisione di caricare pigro potrebbe cambiare, quindi questa è la sezione principale del codice che probabilmente cambierà . ad esempio, in futuro potrebbero essere compressi (o compressi in modo diverso) o archiviati nel cloud anziché in file locali.

Questo è il punto di usare le classi come astrazioni. Le decisioni difficili sono incapsulate nella classe Replay, ma l'API sembra semplice e il client non sa o si preoccupa veramente di come funziona, solo che lo fa. Stai seguendo lo spirito dell'SRP (orribilmente sbagliato e incompreso)

Nota probabilmente il tuo metodo getInputs() deve generare un'eccezione IOException. Questo è probabilmente un esempio di un'astrazione "leaky", ma non tutto il codice può essere perfetto.

    
risposta data 12.11.2017 - 16:24
fonte
0

Supponendo che questi dati siano memorizzati e mai cancellati o modificati durante il runtime, puoi avere il meglio di entrambi i mondi con il seguente design:

  1. Mantieni l'interfaccia
  2. Quando scrivi l'implementazione concreta, aggiungi un metodo privato, forse chiamato getWorldRecordInfo , che ottiene entrambi i campi e li memorizza in una cache. Se la cache è già popolata, salta la chiamata DB.
  3. Programma getWorldRecordTime e getWorldRecordHolder in modo che chiamino getWorldRecordInfo .

In questo modo avrai solo bisogno di una chiamata al database, ma tu preserverai l'integrità delle tue interfacce. Migliorerà anche le prestazioni se questi punti dati devono essere recuperati ripetutamente.

    
risposta data 09.08.2017 - 01:37
fonte

Leggi altre domande sui tag