Interfaccia per enumerare su file

4

Sto provando a progettare un'interfaccia generica per l'enumerazione su un file di elenco. Ogni iterazione aprirà un file, consenti al codice che consuma di accedere al file per eseguire alcune attività, quindi chiude il file.

All'inizio questo sembrava un semplice schema di enumerazione, ma il codice consumante in realtà ha bisogno di conoscere lo stato di ciascun file sia dopo che è stato aperto sia dopo che è stato chiuso. Quindi, piuttosto che avere un singolo metodo MoveNext() la mia interfaccia ha metodi OpenNextFile() e CloseCurrentFile() , ognuno dei quali può restituire uno stato di errore. Inoltre, CloseCurrentFile() ha bisogno di un modo per segnalare se ogni file è stato salvato o meno quando è stato chiuso.

Ecco la mia interfaccia corrente.

public interface IFileEnumerator
{
    //The following properties reflect the current state of the IFileEnumerator
    FileEnumeratorStatus Status { get; }
    string StatusMessage { get; }
    string FileName { get; }
    byte[] FileData { get; }

    //Attempts to open the next file
    FileEnumeratorStatus OpenNextFile();

    //Closes the current file
    FileEnumeratorStatus CloseCurrentFile();
}


public enum FileEnumeratorStatus
{ 
    AtStart, //Files have not been opened
    Opened, //File currently opened
    Opened_Failed, //Failed to open file
    Close_Saved, //File closed and saved
    Closed_NotSaved, //File closed and not saved
    Close_SaveFailed, //File closed saving failed
    AtEnd //No more files
}

Ecco un esempio del codice che consuma:

//FileProcessor is a class which run a ProcesssJob on a set of files returned by a     
//IFileEnumerator and returns a ProcesssingJobResult with results of all the files 
public class FileProcessor
{
    public ProcesssingJobResult RunJob(IFileEnumerator fileEnumerator, ProcesssJob job)
    {
        List<FileProcessingResult> resultList = new List<FileProcessingResult>();

        while (fileEnumerator.OpenNextFile() != FileEnumeratorStatus.AtEnd)
        {
            FileProcessingResult currentFileResult = new FileProcessingResult();
            currentFileResult.FileName = fileEnumerator.FileName;

            if (fileEnumerator.Status == FileEnumeratorStatus.Opened)
            {
                currentFileResult.JobCompleted = job.RunJob(fileEnumerator.FileData);

                var closureStatus = fileEnumerator.CloseCurrentFile();

                if (closureStatus == FileEnumeratorStatus.Close_Saved)
                    currentFileResult.Saved = true;
                else if (closureStatus == FileEnumeratorStatus.Closed_NotSaved)
                    currentFileResult.Saved = false;
                else if (closureStatus == FileEnumeratorStatus.Close_SaveFailed)
                {
                    currentFileResult.Saved = false;
                    currentFileResult.Message = fileEnumerator.StatusMessage;
                }
            }
            else if (fileEnumerator.Status == FileEnumeratorStatus.Opened_Failed)
            {
                currentFileResult.JobCompleted = false;
                currentFileResult.Message = fileEnumerator.StatusMessage;
                currentFileResult.Saved = false;
            }

            resultList.Add(currentFileResult);
        }

        ProcesssingJobResult jobresult = new ProcesssingJobResult();
        jobresult.TimeStamp = DateTime.Now.ToUniversalTime();
        jobresult.Machine = System.Environment.MachineName;
        jobresult.User = System.Environment.UserName;
        jobresult.Results = resultList;

        return jobresult;
    }
}

//A generic task that can be run on a file
public interface ProcesssJob
{
    bool RunJob(byte[] data);
}

//Result of processing one file
public class FileProcessingResult
{
    public bool JobCompleted { get; set; }
    public bool Saved { get; set; }
    public string FileName { get; set; }
    public string Message { get; set; }

}

//The overall result of processing a set of files
public class ProcesssingJobResult
{
    public DateTime TimeStamp { get; set; }
    public String Machine { get; set; }
    public String User { get; set; }
    public List<FileProcessingResult> Results { get; set; }
}

Forse questa è solo una questione di attenta documentazione, ma ho difficoltà a gestire tutti questi diversi stati possibili. Disegnare un diagramma di stato non aiuta in quanto è possibile passare da ogni stato a quasi ogni altro stato. Il codice di consumo deve chiamare CloseCurrentFile () se OpenNextFile () restituisce Open_Failed? Cosa succede se lo fa? Verrà restituito uno stato Chiuso anche se un file non è mai stato aperto? Se CloseCurrentFile () non è stato chiamato dopo OpenNextFile () dovrebbe essere chiuso automaticamente dalla prossima chiamata a OpenNextFile () o dovrebbe essere generata un'eccezione? Preferisco che qualcuno che implementa questa interfaccia non si debba preoccupare di ottenere tutto ciò giusto.

Il problema è molto più semplice se ho un singolo metodo OpenNextFile () che è responsabile sia della chiusura dell'ultimo file aperto, sia dell'apertura del file successivo. Tuttavia, questo singolo metodo deve riportare sia il risultato dell'apertura del file successivo, sia la chiusura dell'ultimo file. Anche questo diventa complicato per il consumo di codice poiché lo stato finale di ogni file non viene determinato fino alla seguente iterazione.

public interface IFileEnumerator_Alternate
{
    string FileName { get; }
    byte[] FileData { get; }
    string StatusMessage {get;}

    //Closes the previous file, and opens the next file
    Result OpenNextFile();
}

public class Result
{
    public bool CurrentFileOpened {get; set;}
    public string CurrentFileName {get; set;}
    public string CurrentFileStatusMessage {get; set;}

    public string LastFileName {get; set;}
    public bool LastFileClosedOk {get; set;}
    public string LastFileClosedStatusMessage {get; set;}
    public bool LastFileSaved {get; set;}
}

C'è un altro modo per vedere questo, o qualche altro modello di progettazione comune che è più adatto a quello che sto cercando di fare? Ecco un diagramma di attività che mostra il processo complessivo.

    
posta Eric 29.10.2014 - 03:19
fonte

3 risposte

4

Che ne dici di affrontare il problema dall'altra parte. Hai una classe, che rappresenta quel bel diagramma di flusso del tuo e questo diagramma di flusso crea "eventi" basati sulle transizioni. Questi eventi possono quindi essere rappresentati come un'interfaccia o eventi:

public interface IFileProcessor
{
    void HandleFile(string fileName, Stream data);
    void OpenFailed(string fileName);
    void CloseFailed(string fileName);
    void Closed(string fileName, bool saved);
    void AllFilesProcessed(); // maybe?
}

Quindi il tuo "enumeratore" accetterà l'istanza della classe che implementa questa interfaccia ed esegue i metodi rilevanti.

string[] files = ...; // what files?
IFileProcessor processor = ...; // how the files should be handled?
var enumerator = new FileEnumerator(files, processor);
enumerator.Run(); // process all files

Questa soluzione sembra molto più semplice, ma leggermente più difficile da capire a causa del controllo invertito.

    
risposta data 29.10.2014 - 09:36
fonte
0

What if instead of using an interface I create an abstract class with abstract methods of each of these steps which concrete classes have to implement

Sì. Un interface non indica come i metodi interagiscono o come elaborare. Questa è solo una tecnica per l'iniezione di dipendenza.

Part of the problem is acquiring the list of files can be a long process. Each file may need to be downloaded, and opened to find nested contents etc..etc. So I want to do the processing as the files are being iterated over rather then opening them twice.

Facciata del documento

Una classe di metadati generale e amp; e associato "quadro" con funzionalità di uguaglianza e confronto personalizzate iniettabili.

public class DocumentFacade : IEquatable<DocumentFacade>, IComparable<DocumentFacade> {
    // this class is more like document metadata, not necessarily a facade design
    // pattern wrapping the Document object itself
    protected EqualityComparer<DocFacade> equalizer;  // customized Equals, CompareTo

    public string Name { get; set; }
    // superset of any and all properties relevant to processing. Covers all
    // document sub classes. 

    public DocStatus OpenStatus { get; set; }  // an enum
    public DocStatus CloseStatus { get; set; }
    public DocClass DocumentClass; // an enum, alternatively Type.Name, or even the Type object itself.

    public BaseDocClass theDoc;  //reference to the document this DocumentFacade represents
                                 // maybe you don't even need this.

   public DocumentFacade (IEqualityComparer<DocFacade> equalizer) {
       if(equalizer == null) throw new NullArgumentException();
       this.equalizer = equalizer;
   }

    public override bool Equals(object that) {
       if(that == null) return false;
       if(that.DocumentType != this.DocumentType) return false;
        return equalizer.Equals(this, other);
    }

    // the IEquatable implementation
    public bool Equals(this, y) {
        return Equals(y);
    }
} 


// A class for each way we want to compare Documents; each sub-class I suppose.
// MSDN says inheriting is the way to go, vice interface implementation.
public class DocFacadeEqualizerTypeA : IEqualityComparer<DocumentFacade> {
    public override bool Equals (DocFacade x, DocFacade y) {
        // customized implementation
    }
    // ditto CompareTo override
}

public class DocumentFacadeCollection : List<DocFacade> {

    public bool Equals (DocFacade x, DocFacade y) {
        if(x == null) return false;
        return x.Equals(y);
    }

    // lots of inherited List stuff makes this class really handy.
    public DocFacadeCollection GetOpenStatus(DocStatus thisStatus) {
        Find( x => x.OpenStatus == thisStatus);
    }

    public DocFacadeCollection FindAllDuplicates (DocFacadeCollection otherList) {
         DocFacadeCollection duplicates = new DocFacadeCollection();
         duplicates.AddRange( this.FindAll( x => otherList.Contains( x )).AsEnumerable());
         return duplicates;
    }
}

public static class DocumentDNAAnalyzer {
    // this may be a factory, or perhaps more likely, the front of
    // of a Builder pattern implimentation
    // Besides a customized DocFacade object, we may also be able to
    // provide stuff for abstract template.

    public static DocFacadeFactory = new DocFacadeFactory();

    public static DocFacade Create(object TheDocument) { 
        return DocFacadeFactory.Create(TheDocument));
    }
}

Vedi questo thread per un ottimo GetHashCode () Implementazione

    
risposta data 31.10.2014 - 17:03
fonte
0

Penso che dovresti separare l'enumerazione dei file e passare ogni singolo file.

Esiste già un'interfaccia in .Net per l'enumerazione: IEnumerable<T> , quindi la userei.

Questo lascia il lavoro con un singolo file: sembrerebbe più o meno come IFileEnumerator , tranne che con le parti dell'enumerazione rimosse.

Inoltre, non penso che l'apertura e la chiusura debbano utilizzare lo stesso enum di stato.

public interface IFile
{
    string FileName { get; }
    byte[] FileData { get; }

    bool Open();

    FileCloseStatus Close();
}

public enum FileCloseStatus 
{ 
    Saved, //File closed and saved
    NotSaved, //File closed and not saved
    SaveFailed, //File closed saving failed
}
    
risposta data 02.11.2014 - 13:12
fonte

Leggi altre domande sui tag