Design: metodo che carica i dati da uno dei due formati di file?

4

Dire che ho una classe Person:

class Person
{
  string FullName;
  int Age;
}

E ho bisogno di caricare un elenco di Person da un file di testo normale. Il file di testo potrebbe essere in uno dei due formati. Voglio un metodo come LoadFromFile (percorso stringa) che rilevi 1) il formato del file 2) analizzi in modo appropriato il tipo di file e 3) restituisca un elenco di Persone.

La soluzione rapida e sporca sarebbe di avere un singolo metodo LoadFromFile dove leggo l'intestazione del file, posso determinare quale tipo di file è, e quindi, mantenendo il file aperto, accendi il tipo e leggetelo appropriatamente .

D'altra parte, il fatto che siano due diversi "tipi" di file mi fa pensare che sia un buon candidato per il polimorfismo. Questo sarebbe particolarmente buono se ci fosse un terzo tipo di file. Tuttavia, avere classi separate fa sembrare che sarebbe più difficile mantenere il file aperto dalla fase di "rilevamento del tipo" alla "fase di analisi". Anche se forse voler mantenere il file aperto è solo un'ottica prematura.

Voglio che il codice client chiami semplicemente LoadFromFile (percorso) e rilevi il tipo di file e lo carichi in modo appropriato.

Che cos'è un buon design per questo (in termini di nomi di classi e nomi di metodi)?

    
posta User 23.07.2011 - 02:15
fonte

3 risposte

5

Modello di strategia

Il modello di strategia definisce una famiglia di algoritmi, incapsula ciascuno e li rende intercambiabili. La strategia consente all'algoritmo di variare in modo indipendente dai client che lo utilizzano.

Ecco alcuni esempi eccellenti del modello di strategia

Come puoi vedere dal primo link qui sopra, puoi individuare il tipo di file nell'intestazione nel tuo metodo principale, quindi inserire la versione corretta della strategia nel contesto e quindi chiamare il contesto.

Se vuoi davvero, puoi scrivere un metodo di fabbrica per creare automaticamente il tuo contesto.

Quindi potresti chiamare qualcosa come la seguente

var context = FileFactory.CreateContext(myFile);
context.Parse();

Un metodo factory potrebbe essere un po 'eccessivo per questo, tuttavia, questo sarebbe un giudizio da parte tua. Tuttavia, l'utilizzo del modello di strategia come sopra offre la flessibilità di aggiungere qualsiasi numero di nuovi tipi di file da analizzare. Inoltre, la fabbrica ti darà la possibilità di astrarre tutto il polimorfismo dal tuo codice logico.

Modifica

Ecco cosa si otterrebbe utilizzando un modello di strategia combinato con un metodo di fabbrica.

public interface IFileParserStrategy
{
    IList<Person>  Parse(String path);
}


public class FileFormatNo1 : IFileParserStrategy
{    
   private string _path; 
    public FileFormatNo1(String path){
      _path = path;
    }

    public IList<Person> Parse()
    {
        // Actual Logic for handling File Format #1 
        // and returning a IList<Person>   
    }
}

public class FileFormatNo2 : IFileParserStrategy
{     
    private string _path; 
    public FileFormatNo1(String path){
      _path = path;
    }

    public IList<Person> Parse(String path)
    {
        // Actual Logic for handling File Format 2
        // and returning a IList<Person>   
    }
}

public interface IFileParserContext 
{ 
    public IList<Person> Parse();
}

public class FileParserContext : IFileParserContext
{
    private IFileParserStrategy _strategy;

     public FileParserContext(IFileParserStrategy fileParserStrategy)
     {
         _strategy = fileParserStrategy;
     }

     public IList<Person> Parse()
     {
         return _strategy.Parse();
     }
}

public static class FileParserFactory
{
    public static IFileParserContext CreateContext(String path)
    {
           FileParserContext context = null;

           var checkFileFormat = File.ReadAllLines();

            if(checkFileFormat.Contains("FileFormatNo1"))
              context = new FileParserContext(new FileFormatNo1(path));

            if(checkFileFormat.Contains("FileFormatNo2"))
               context = new FileParserContext(new FileFormatNo2(path));

             if(context == null)
                throw new FileFormatException("File is not in a readable state");

             return context;            
    }       
}


public static void Main()
{    
    var filePath = "someFile.txt";
    var context = FileParserFactory.CreateContext(filePath);    
    var personList = context.Parse();
}

Aggiorna

È possibile modificare la strategia precedente per passare un FileStream se si desidera aprire il file solo una volta. È possibile modificare FileParserFactory.CreateContext per accettare un FileStream come parametro e rifattorizzare le strategie per accettare FileStream anziché filePath.

public static void Main()
{    
    var filePath = "someFile.txt";
    List<Person> personList;

    using(var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) 
    {
        var context = FileParserFactory.CreateContext(fileStream);    
        personList = context.Parse();
    }        
}
    
risposta data 23.07.2011 - 12:02
fonte
3
  1. Apri file
  2. Leggi intestazione
  3. Determina il tipo
  4. Invio in una delle due funzioni del caricatore specializzate, passandole con l'handle di file già aperto
  5. Chiudi file

Il dipatcher può essere una semplice istruzione switch o, se il linguaggio lo consente e ha senso, un dizionario che associa i tipi di intestazione ai puntatori di funzione (delegati, funtori anonimi, ...).

Passaggio 5, penso che dovrebbe essere fatto nel caricatore principale, non in quelli specifici del formato: dovrai comunque chiudere il file, e questo puoi inserirlo elegantemente in un blocco finally nel caso in cui il loader genera un'eccezione.

Un'altra considerazione è se si debba o meno riavvolgere il file prima di chiamare il caricatore specifico per il formato; se intendi supportare formati completamente incompatibili; riavvolgere significa che il tuo caricatore specifico per il formato può essere richiamato direttamente quando conosci già il formato, ma richiede anche la lettura dell'intestazione due volte (o saltandola la seconda volta, se è fissa). Inoltre, se vuoi leggere da un'origine che non consente di cercare indietro (socket di rete, pipe, ecc.) Non puoi farlo (a meno che non bufferizzi il flusso di input).

In questo modo, separerai le funzionalità in un modo che ha senso, senza alcun fluff. È anche piuttosto mantenibile - se è necessario aggiungere un altro formato di file, basta scrivere la funzione caricatore e aggiungerla al dispatcher. Per un po 'più incapsulamento, puoi estrarre il dispatcher dal caricatore principale e metterlo nella sua funzione (nello spirito del modello di fabbrica).

Se vuoi, puoi OOP il tutto: avvolgere il caricatore principale in una classe, scrivere un'interfaccia per i singoli caricatori, creare le classi di caricatori che implementano l'interfaccia del caricatore, rendere il committente una classe di produzione che restituisca un caricatore . L'idea di base è la stessa però; i blocchi predefiniti sono caricatore principale, caricatori specifici per formato e un dispatcher.

    
risposta data 23.07.2011 - 09:28
fonte
2

il caricatore polimorfico è una configurazione interessante (e puoi ottenere dei punti extra per un incarico;))

questo vorrebbe dire che hai bisogno di un'interfaccia con isThisFormat() load(istream) e un'implementazione di una classe per ogni formato

il problema principale che riesco a vedere è come creare il rilevamento

un modo sarebbe supporre che i primi x byte siano sufficienti per rilevarlo e semplicemente leggerlo e passarlo (e resettare il flusso successivamente)

un altro modo sarebbe quello di passare il flusso stesso e assicurarsi che il flusso (ricercabile / contrassegnabile) venga reimpostato all'inizio del file quando restituisce false (come parte del contratto del metodo)

quindi puoi chiamarlo come

List<Person> LoadFromFile(string path){
    istream stream = ;//open file
    try{
        foreach(PersonLoader loader in registeredPersonLoaders){
             if(loader.isThisFormat(stream)){//looping is the type detection
                 return loader.load(stream);//the if branch is the parse phase
             }
        }
        throw UnkownFormatException(path);//or return empty_list
    }finally{
        stream.close();//cleanup
    }
}

in questo modo non devi preoccuparti che il file rimanga aperto e l'implementazione di PersonLoader non debba preoccuparsi di ciò

    
risposta data 23.07.2011 - 02:35
fonte

Leggi altre domande sui tag