Dipendenza ciclica con la classe logger e filesystem

8

Ho una dipendenza ciclica nella mia architettura.

Supponiamo che ci siano i seguenti moduli:

FileSystem (utilizza Logger)

Gestisce le operazioni sui file come leggere, scrivere file, ecc. Questo modulo dovrebbe registrare le sue azioni. Il logger viene iniettato tramite l'integrazione delle dipendenze.

NullLogger (implements Logger)

Elimina tutti i messaggi di registro.

FileLogger (implements Logger)

Scrive i messaggi di log sul file system. Dovrebbe utilizzare internamente il modulo FileSystem per ridurre la duplicazione del codice. (Vedi dove va?)

ConsoleLogger (implements Logger)

Scrive i messaggi di log direttamente nella console.

Il problema (se non è chiaro ora):

  • Passo ConsoleLogger a FileSystem e tutto funziona bene.
  • Passo FileLogger a FileSystem e poof! Non funzionerà perché questi due moduli dipendono ricorsivamente l'uno dall'altro, quindi non potrò istanziarne nessuno.

Posso solo pensare a un possibile modo per evitare il problema:

FileLogger crea un'istanza del proprio FileSystem con una NullLogger (o qualsiasi altro Logger eccetto FileLogger ). Svantaggio: non sarei in grado di vedere alcun messaggio di registro (nel file) dal FileLogger stesso.

Ma non sono sicuro se questa sia la strada da seguire.

    
posta d3L 14.04.2017 - 06:24
fonte

3 risposte

17

I pass FileLogger to FileSystem, and poof! It won't work because those two modules are recursively dependent on each other so I won't be able to instantiate either of them.

Penso che sia discutibile se un modulo FileSystem abbia davvero bisogno di registrazione al suo interno, o se questa preoccupazione dovrebbe essere risolta su un livello diverso, come suggerito da Ben Cotrell, ma comunque, supponiamo che il tuo requisito sia giustificato per qualche ragione.

I registri delle chiamate FileSystem devono essere scritti su un file diverso rispetto al file di registro utilizzato, ad esempio, da un'applicazione che crea un'istanza di un registratore di file per uno scopo diverso. Pertanto, in questo scenario dovrebbero esserci due oggetti FileLogger , ciascuno associato a un diverso file . E poiché ognuno degli oggetti FileSystem contiene un riferimento a uno specifico registratore di file, ciò comporta la necessità di avere anche due oggetti file system diversi , dove il primo viene inizializzato con un registratore nullo e il secondo con il logger per le chiamate al file system. In pseudo codice:

 FileSystem nonLoggingFileSystem = new FileSystem(new NullLogger());

 FileLogger fileSysCallsLogger("file_system_calls.log", nonLoggingFileSystem);

 FileSystem fileSystem = new FileSystem(fileSysCallsLogger);

 FileLogger applicationCallsLogger('application_calls.log', fileSystem);

Ora non esiste alcuna dipendenza ciclica tra gli oggetti coinvolti e fileSystem e applicationCallsLogger possono essere utilizzati in modo sicuro all'interno dell'applicazione, senza alcuna ricorsione.

    
risposta data 14.04.2017 - 09:54
fonte
3

Aggiornamento per chiarire l'intento di questa risposta: La domanda identifica due responsabilità distinte secondo il principio di responsabilità singola - registrazione e file I / O. Una delle opzioni presentate nella domanda è iniettare un NullLogger che non fa nulla. Un'alternativa all'iniezione di un do-nothing NullLogger potrebbe essere quella di scrivere una classe standalone per gestire il limite che non ha interesse nel logging, quindi gestire la registrazione più in alto ed eliminando la necessità di un NullLogger .

Considera di suddividere il comportamento del tuo FileSystem tra il codice che esegue effettivamente l'I / O e quel codice che decide se / quando eseguire qualsiasi registrazione correlata (ad esempio LoggedFileSystem ).

Potresti avere una semplice classe FileSystem che non esegue alcuna registrazione e viene utilizzata solo dal Logger e da LoggedFileSystem , mentre il resto del sistema utilizza LoggedFileSystem per tutti i suoi I / O file .

Ad esempio:

// The "raw" I/O class.
public class FileSystem 
{
    public byte[] Read(string filename) { /* ..etc. */ }
    public void Write(byte[] data, string filename) { /* ..etc. */ }
}

// Used by the rest of the system for its I/O operations
public class LoggedFileSystem
{
    private FileSystem _fileSystem = new FileSystem();

    public byte[] Read(string filename) 
    { 
        try 
        {
            Logger.Debug($"Reading file {filename}...");
            var data = _fileSystem.Read(fileName);
            Logger.Debug($"Successfully read {filename} - {data.Length} bytes");
            return data;
        }
        catch (Exception ex)
        {
            Logger.Error($"Exception handled reading {filename}", ex);
            throw;
        }
    }

    public void Write(byte[] data, string filename) 
    { 
        try 
        {
            Logger.Debug($"Writing {data.Length} bytes to {filename}");
            var data = _fileSystem.Read(fileName);
            Logger.Debug($"FileSystem.Write succeeded.");
        }
        catch (Exception ex)
        {
            Logger.Error($"Exception handled writing to {filename}", ex);
            throw;
        }
    }
}

In sintesi, questo suggerimento alternativo è funzionalmente equivalente a uno dei suggerimenti originali di iniezione di NullLogger into a FileSystem '. Il risultato è

  • Una classe FileSystem senza registrazione,
  • Una classe LoggedFileSystem con gli stessi metodi di FileSystem (da utilizzare allo stesso modo da una prospettiva esterna), ma che gestisce anche la registrazione.

Aggiorna : Come nota a margine: potresti ancora inserire il Logger nel LoggedFileSystem come per alcune delle altre soluzioni alternative, nel caso sia necessario per qualsiasi motivo:

public class LoggedFileSystem
{
    private FileSystem _fileSystem = new FileSystem();
    private ILogger _logger;

    public LoggedFileSystem(ILogger logger)
    {
        _logger = logger;
    }
} 
    
risposta data 14.04.2017 - 09:20
fonte
1

Finché i tuoi oggetti non fanno alcun lavoro utile nei loro costruttori, è possibile risolvere le dipendenze cicliche nell'integrazione delle dipendenze usando l'iniezione di proprietà.

Ad esempio, se il tuo FileLogger espande il suo file fino a quando non viene chiamato per registrare qualcosa, puoi inizializzare il tuo sistema in questo modo:

FileLogger logger = new FileLogger();
FileSystem filesystem = new FileSystem (logger);
logger.setFileSystem (filesystem);

Puoi anche scegliere di interrompere il ciclo in un altro modo, inserendo il logger del file system come proprietà.

Sarebbe importante che FileLogger abbia un buon modo di ripristinare se usato prima che la proprietà del file system sia impostata (magari memorizzando i messaggi in un buffer e registrandoli alla prima opportunità).

    
risposta data 14.04.2017 - 10:21
fonte

Leggi altre domande sui tag