Come fornire un oggetto ubiquitario senza includerlo in ogni elenco di parametri

2

Sto scrivendo una piccola classe C ++, Block , per trasformare i dati serializzati in una struttura di memoria e fornire i dati strutturati ai chiamanti attraverso diversi metodi di accesso. Ho cercato di mantenere il suo ambito specifico e limitato. Gli utenti della classe sono di livello molto basso, anch'essi sono molto focalizzati e hanno il minor numero possibile di dipendenze esterne.

Questo è il modo in cui mi è stato insegnato a progettare le cose, se possibile. Riducendo al minimo le dipendenze e il featurism strisciante, è più facile testare l'unità e più facile da riutilizzare.

Il problema è che la mia classe dipende dalla classe di qualcun altro, Metadata . Anch'essa fa una cosa molto specifica: legge i dati che definiscono le caratteristiche del flusso di dati che trasformerò da una tabella di database e me la passerà. La sua classe controlla gli errori mySQL, che dovrebbero essere rari e registra eventuali errori su un oggetto Log .

Questo oggetto Registro viene visualizzato in tutte le applicazioni della nostra azienda. Istanziare è un grosso problema: vuole numeri di lavoro, vuole molte informazioni di configurazione dal database che normalmente vengono messe in produzione dagli account manager usando una GUI. Devi fare molto lavoro prima che il tuo programma possa istanziare il Log. Eppure questa minuscola classe di basso livello ( Metadata ) con un piccolo compito vuole che venga passata, da me. Il mio oggetto non ha certamente alcun business che istanzia il registro , quindi devo prenderlo come parametro da chiunque mi chiami. E così via, su per la gerarchia chiamante.

Posso capire perché la direzione vuole che una classe incapsuli e standardizzi la registrazione dei messaggi. Ma la necessità che venga passata a, e attraverso, quasi ogni metodo è estremamente brutto, e rende molto più difficile il testing e il riutilizzo.

Questo tipo di problema deve essere abbastanza comune. Come può essere fatto senza ingombrare la firma di ogni metodo che scrivi? È un caso legittimo per i Globali? Esiste qualche tipo di approccio orientato agli oggetti? E questo è un God object ?

    
posta Chap 13.07.2013 - 08:02
fonte

4 risposte

3

Dato che il logger è una sorta di infrastruttura, è corretto non passarlo in tutte le chiamate.

Nel mondo java e c # di solito ogni classe che ha bisogno di un logger ha un logger statico privato che la classe ottiene da una fabbrica di logger

using log4net;
namespace MyNamespace
{
   public class MyClass
   {
      private static readonly ILog log = log4net.LogManager.GetLogger("MyNamespace.MyClass");

      public void someFunction() {
         log.debug("someFunction was called");
      }

Questo approccio presuppone che il logger rimanga sempre nella stessa istanza, quindi l'overhead delle prestazioni è minimo (solo una chiamata factory per classe).

In alternativa è possibile utilizzare un logger più dinamico che può essere modificato in fase di esecuzione tramite un metodo statico pubblico (singelton)

namespace MyNamespace
{
   public class MyClass
   {
      public void someFunction() {
         Global.getLogger().debug("someFunction was called");
      }

[aggiornamento 2013-07-14]

rispondendo alle domande di chaps:

Nel classlibrary log4net ogni classe ottiene la propria istanza di logger. Il parametro in log4net.LogManager.GetLogger("MyNamespace.MyClass") è il nome del logger. attraverso la configurazione puoi dire al logger "MyNamespace.MyClass"   - essere abilitato / disabilitato   - che ogni riga di log deve essere preceduta da "MyNamespace.MyClass"

    
risposta data 13.07.2013 - 09:20
fonte
3

Un oggetto di Dio fa tutto per tutti. È un programma non strutturato che si nasconde sotto le spoglie di una classe OOP, ma che è troppo grande per qualsiasi misura.

Al contrario, sia la tua classe che il Log sono classi di dimensioni ragionevoli che fanno una cosa e la fanno bene. Il tuo unico problema è che il registro deve essere disponibile ovunque. Se questo è davvero il caso, ed è chiaro che ci sarà un solo oggetto Log nel processo, renderlo globale è la cosa giusta da fare.

(C'è un sacco di odio per le variabili globali (e per gli oggetti singleton) in base al pasticcio orribile che fanno quando vengono usate per qualcos'altro, come risultati intermedi, codici di errore, o anche contatori di loop (brivido), ma come sempre c'è una gamma di cose ragionevoli e irragionevoli che puoi fare con loro. Il tuo caso sembra quasi l'uso più ragionevole per un globale che riesco a pensare.)

    
risposta data 13.07.2013 - 09:19
fonte
2

Potrebbe non essere immediatamente ovvio, ma hai appena descritto gran parte delle motivazioni alla base dell'introduzione della gestione delle eccezioni in molte lingue. Si dispone di un componente di basso livello che in realtà non dovrebbe imporre la politica in cui l'errore utilizza i dettagli di alcune particolari classi di registrazione. Piuttosto, il lavoro di Metadata dovrebbe semplicemente essere 1) fare il suo lavoro, o 2) il segnale fallito. La conoscenza della registrazione dovrebbe essere limitata a qualcosa di molto più in alto nello stack (sia in senso figurato che, in questo caso, letteralmente) che si occupa di cose come i log delle applicazioni.

Quindi, il design preferibile è che Metadata lancia semplicemente un'eccezione se non può fare il suo lavoro. Ad alcuni (molto) livello superiore, il codice che invoca qualcosa che usa Metadata imposta un gestore per l'eccezione che Metadata genera e nel gestore registra il risultato. Usando, al momento, la sintassi C ++ (ma l'idea di base si applica ugualmente a molti altri linguaggi, otteniamo qualcosa del genere:

Log log(lots,of,inputs);

try {
     Metadata metadata(input);
     Block block(metadata);
     block.write(output);
}
catch (metadata_exception const &e) {
    log(e.whatever());
}

Risultato: Block e Metadata rimangono piccoli, puliti e riutilizzabili. La registrazione avviene a un livello appropriato, invece di passare l'oggetto Log a tutte le funzioni di basso livello.

Nota comunque un punto: questo vale qui in gran parte a causa di un punto cruciale: hai chiarito che la registrazione avviene solo in caso di errore. Lanciare un'eccezione in caso di errore (ad esempio, non si può davvero continuare quello che stavi cercando di fare) è del tutto appropriata e ragionevole. Questo modello non potrebbe funzionare se (ad esempio) si stavano registrando eventi di routine. Se (per esempio) volessi registrare con successo leggendo i metadati dal database, questo non sarebbe affatto appropriato.

Dal momento che si sta registrando un errore, tuttavia, supporrò che (se disponibile) lanciare un'eccezione sia davvero il modo corretto di gestire la situazione - decisamente meglio che dipendere da un oggetto globale Log , con una fabbrica di log , ecc.

Questi sono, presumo, i kludges per ridurre il dolore di una struttura scelta male. Sono approssimativamente equivalenti alle bende per uso intensivo per ridurre al minimo la perdita di sangue dopo aver tagliato il braccio. Sì, se ti tagli il braccio, buone bende potrebbero salvarti la vita - ma è ancora un lotto migliore per non tagliare il braccio in primo luogo.

    
risposta data 17.07.2013 - 16:56
fonte
0

Il modo principale di trattare con un oggetto di log "globale" sarebbe utilizzare Iniezione delle dipendenze.

Il modo leggermente meno convenzionale sarebbe utilizzare la programmazione orientata all'aspetto.

La soluzione interessante è quella di reframe logging come istanza di Writer Monad. Ecco un esempio in Scala: link . Nota: il concetto è indipendente dal linguaggio, Scala è semplicemente usato come una notazione conveniente qui.

    
risposta data 14.07.2013 - 06:07
fonte

Leggi altre domande sui tag