Oggetto console statico o dipendenza da iniezione?

6

Per un progetto corrente ho creato una classe Console che avvolge il solito cout del C ++, scrive l'output in un file o usa Console::WriteLine a seconda dell'ambiente. Attualmente ho impostato il mio codice base per iniettare un oggetto console in ogni classe che uso, facendo in modo che tutti estendano una classe base: Debuggable . All'inizio questo sembrava un grande piano, ma ora devo passare la console ad ogni oggetto e assicurarmi che sia stato creato prima che tutti abbiano dei ripensamenti.

Solitamente le console sembrano essere gestite con oggetti statici, quindi sto considerando di cambiare la mia implementazione per usare un'istanza statica della console che tutti gli oggetti possono usare ma ritengo che questo rimuova un elemento di controllo, non posso avere più di una console potenziale con diversi formati e target.

Capisco che ci sia un sacco di discussioni sull'iniezione delle dipendenze, mi interessa in particolare nel caso di utilità come una console.

    
posta sydan 27.04.2015 - 11:19
fonte

2 risposte

2

Di solito quando c'è un oggetto, che deve essere usato ovunque (come la tua classe di logger), usare il singleton ha senso. E questo è uno dei rari casi in cui il singleton non è un pattern anti.

Puoi implementarlo in tal caso che è possibile creare un mock (se stai andando ai test unitari).

Quindi, qualcosa del genere:

class Logger
{
public:

    typedef Debugable* (Fnc)();

    static Debugable& Instance()
    {
        static Debugable* p = f();
        return *p;
    }

    void SetCreator( Fnc newf ){ f = newf; }


private:
    static Debugable* DefaultCreator(){ return 0; }
    static Fnc f = DefaultCreator();
};

Per prima cosa devi impostare la funzione creatore da qualche parte (fallo una sola volta):

Debugable* CreateLogger()
{
   return new StdOutputLogger;
};

//...
// somewhere
Logger::SetCreator( CreateLogger );

quindi usalo come un singleton:     Logger :: Instance () < < "registra questo messaggio";

Nei test unitari, devi creare un oggetto fittizio:

struct MockLogger: Debugable pubblico {   // metodi };

Impostalo nella funzione setUp ():

MockLogger mock;
Debugable* CreateMock()
{
   return new StdOutputLogger;
};

void setUp()
{
  Logger::SetCreator( CreateMock );
}

quindi controlla gli accessi alla simulazione.

    
risposta data 27.04.2015 - 11:26
fonte
0

Nota: Inizialmente ho risposto a questa domanda senza rendermi conto che si tratta dell'uso di singleton a scopo di debug . Quindi, la prima parte della mia risposta è per l'uso dei singleton in generale, non per il debugging. La seconda parte della mia risposta affronta il problema del debugging. (Ho aggiunto ad esso un paio di punti che ho inserito nei commenti.)

Parte 1 - singleton in generale (senza riguardo al debugging)

La mia raccomandazione sarebbe quella di non andare con un singleton. La mia esperienza è che "il singleton" non è un modello, ma un anti-modello per sua stessa natura. (Ma non trasformiamo questo in ancora un altro episodio della guerra singleton, vero?)

Dici di essere "interessato in particolare nel caso di utilità come una console". Ho paura che una console non sia un'utilità. È uno stream a cui si invia l'output.

  • Il testo inviato viene bufferizzato, quindi ha lo stato.

  • Chiamarlo contemporaneamente da più thread potrebbe non causarne il crash, ma si tradurrà in linefeed errati e linee incomprensibili, quindi non si può davvero pensare che siano suscettibili di multi-threading.

  • Anche se per alcuni è conveniente considerarlo un singleton, concettualmente non lo è, come dimostra il fatto che è possibile reindirizzare ad altri dispositivi come file, stampanti, ecc.

Quindi, puoi trattarlo come ciò che è realmente, (un flusso di output, passato a chi ha bisogno di produrre materiale), o trattarlo come una cosa speciale pubblica globale unica nel suo genere, la natura di di cui ognuno ha una conoscenza approfondita, e quindi fare affidamento sulla stregoneria fornita dalla biblioteca e dal sistema operativo sotto le scene per annullare le ipotesi e farlo funzionare come il flusso di output che è realmente, per quanto riguarda il resto del sistema .

È vero, potresti finire per renderlo un singleton a causa di pragmatiche preoccupazioni di convenienza e correttezza, ma suppongo che se la convenienza fosse la tua preoccupazione principale, allora non ti porrei la domanda in primo luogo. Quindi, questa risposta è che in realtà esistono altri ingegneri oltre a te, come me, che perseguiranno tenacemente l'approccio "fai bene, evita i singleton".

Parte 2 - singleton per il debug

Sembra che mi sia sfuggito il punto sul debug nella domanda originale. Quando si tratta di eseguire il debug, non ho obiezioni sull'uso dei singleton. Vorrei usare un singleton come helper per il debug, non userei un singleton per nient'altro. Ma poi di nuovo, non testiamo gli helper del debugging. Se richiede un test, non è solo un helper per il debug.

In passato ho provato a passare le installazioni di debug come dipendenze agli oggetti nello stesso modo in cui passo alle dipendenze formali (dipendenze che fanno parte del design) e sono giunto alla conclusione che a) è un grande dolore , b) aggiunge più complessità di quella richiesta dal design. Quando le dipendenze di debug sono trattate con la stessa dignità che le dipendenze di design vengono trattate, iniziano ad apparire come se fossero parte del design, mentre non lo sono.

Ad esempio, supponi di avere una collezione che è ordinabile e vuoi passarla a un comparatore. Vale veramente la seccatura di rendere il comparatore un oggetto in modo da poter inserire la dipendenza da debugging in esso? E non è strano ora che il comparatore sia diventato un'entità a sé stante, come se facesse parte del design, mentre in realtà l'unica ragione per cui è un'entità è avere un riferimento alla funzione di debugging ?

Quindi, se è solo per il debug, (che, per inciso, significa che non richiederà test), poi vai avanti e usa un singleton, va bene.

    
risposta data 27.04.2015 - 11:54
fonte

Leggi altre domande sui tag