Sovraccarico dell'utilizzo di singleton grandi con tutti i file inclusi

1

Ho diversi gestori di sottosistemi per vari usi, ad esempio:

AudioManager
CollisionManager
InputManager
etc.

All'inizio volevo che fossero tutti singletons , tuttavia ora voglio rendere l'architettura un po 'più pulita. L'idea principale è di usare solo un grande singleton (ad esempio System) e conterrà tutti questi oggetti.

Quindi l'interazione sarebbe come questa:

System::getAudioManager()->playSound("Explosion");

//Before it was like this
// AudioManager::playSound("Explosion");

Tuttavia, ora le classi che devono utilizzare questi "manager" devono ottenere l'accesso all'istanza della classe effettiva, con singleton userei solo un include in un file che è necessario:

#include "AudioManager.hpp"

Ora devo fare questo:

#include "System.hpp"

Significa che devo includere il file Singleton principale che contiene tutti gli include, tutti gli altri sottosistemi creati e usati. Ha qualche overhead (prestazioni) o contro?

    
posta Pins 24.10.2017 - 22:07
fonte

2 risposte

6

Entrambe queste sono un'enorme violazione del principio di segregazione dell'interfaccia. Rende anche le tue dipendenze implicite piuttosto che esplicite. Questo ha un costo che non noterai finché non proverai a modificare il tuo sistema esistente. Sarà molto difficile spostare altrove parte del sistema senza rintracciare e riscrivere il modo in cui accede a quelle cose da cui dipende.

Se non ti interessa, allora va bene. Andare fuori di testa. Per favore, non chiedermi di ripararlo più tardi.

Perché? Perché l'ho già visto prima. Si chiama modello del localizzatore di servizi. Funziona. Ma ci sono alternative migliori.

    
risposta data 25.10.2017 - 00:40
fonte
0

Se vuoi davvero seguire il percorso del singleton rispetto all'iniezione delle dipendenze, che consiglio vivamente di sbagliare a fare quanto CandiedOrange, la mia seconda soluzione non è molto migliore a mio parere.

Se in effetti nel tuo sistema ci sono oggetti persistenti e accessibili a livello globale che sono pigri inizializzati e aprono la scatola di Pandora, potresti sfruttare questa idea. Mi piace la soluzione di avere semplicemente, a questo punto:

play_sound("Explosion");

... un piccolo codice carino, come i giorni del turbo C, che è facile per i principianti comprendere con frammenti di codice minimalisti per applicazioni minuscole senza comprendere livelli di astrazione .... così come possiamo avviare un'applicazione di console e produrre globale cout immediatamente senza costruire noi stessi un oggetto console e doverlo passare come parametro.

Se hai degli stati permanenti accessibili a livello globale, potresti anche usarlo per creare questa API cutesy che puoi invocare ovunque. C'è almeno un beneficio carino per essere in grado di avviare un nuovo progetto e immediatamente invocare funzioni come l'output in una console, la riproduzione di un suono o il tracciamento di una linea sullo schermo senza inizializzare, acquisire e trasferire risorse. Tutto ciò può essere fatto con una sintassi procedurale molto semplicistica e non come Drawer::get().draw_line(...) , semplicemente draw_line(...) .

Perché mi interessa la definizione di System in questo contesto o anche la definizione completa di AudioManager e abbiamo dipendenze in fase di compilazione per entrambi? Voglio suonare un suono, il gioco è fatto. L'implementazione di play_sound potrebbe ancora recuperare l'istanza singleton per il tuo System e recuperare il AudioManager da esso e richiamare il metodo play_sound su di esso, ma questo può essere un dettaglio di implementazione di play_sound . Se non mi occupo di casi specifici di uno stato, che non sarò se ci si suppone che sia stato istanziato uno stato globale persistente una volta, allora mi preoccuperò solo di cosa fare, non di chi lo fornisce. Voglio solo chiamare play_sound e avere una riproduzione audio. Non mi interessa chi o cosa fornisce la funzionalità se non ho bisogno di avere queste informazioni.

void play_sound(const string& sound)
{
    System::getAudioManager()->playSound(sound);
}

Quando vedo gli sviluppatori diventare felici con i singleton e Singleton::get().do_something() chiama dappertutto, la prima cosa che viene in mente è perché non solo do_something() e rendere il richiamo del singleton un dettaglio di implementazione e nascondere il singleton e la sua definizione del tipo dal client? Non riesco a vedere nessuna conseguenza negativa per fare ciò che non è già stato introdotto dallo stesso singleton.

Does it have some overhead (performance) or cons?

Quelle chiamate a get() a singleton possono essere relativamente un po 'costose se le stai facendo in massa in un ciclo che fa solo banali cose di basso livello con l'oggetto singleton. Ciò diventa più economico se si rinuncia alla costruzione pigra e si inizializza tutto in modo tale da garantire che venga costruito già nel momento in cui il client tenta di recuperarlo. Anche rendendo il metodo singleton get() thread-safe se il costrutto pigro non è del tutto banale (es: double check locking). È più semplice evitare la costruzione pigra piuttosto che cercare di rendere get() thread safe quando si tratta di costrutti pigri, il che produrrà anche una soluzione più efficiente che rende il recupero di singleton molto economico quando il metodo get() non deve più occuparsi dell'inizializzazione pigra .

La migliore soluzione SE non è ancora quella di usare i singleton, ma se le prestazioni sono un problema e tu insisti a seguirle, almeno aiuta a rinunciare all'inizializzazione pigra e ad inizializzarle in anticipo prima delle chiamate a get() .

    
risposta data 30.11.2017 - 00:28
fonte

Leggi altre domande sui tag