2009-03-31 11 views
16

Credo che il modello di progettazione del metodo factory sia appropriato per quello che sto cercando di fare, ma non sono sicuro di quanta responsabilità (conoscenza delle sottoclassi create) a darlo. L'esempio di utilizzo del factory method pattern su Wikipedia descrive la situazione in cui mi trovo in quasi esattamente:Come fa una fabbrica a sapere quale tipo di oggetto creare?

public class ImageReaderFactory 
{ 
    public static ImageReader getImageReader(InputStream is) 
    { 
     int imageType = figureOutImageType(is); 

     switch(imageType) 
     { 
      case ImageReaderFactory.GIF: 
       return new GifReader(is); 
      case ImageReaderFactory.JPEG: 
       return new JpegReader(is); 
      // etc. 
     } 
    } 
} 

La mia domanda è, cosa fa la funzione figureOutImageType assomiglia? In questo specifico esempio, presumo che controlli un'intestazione di file nello InputStream per determinare in quale formato di immagine si trovano i dati. Vorrei sapere se lo ImageReaderFactory sa come analizzare le intestazioni di file e determinare se il tipo di file è GIF , JPEG, ecc. O se chiama una funzione all'interno di ciascuna classe Reader che consente di sapere quale tipo di immagine è. Qualcosa di simile, forse:

int figureOutImageType(InputStream is) 
{ 
    if(GifReader.isGIF(is)) 
     return ImageReaderFactory.GIF; 
    else if(JpegReader.isJPEG(is)) 
     return ImageReaderFactory.JPEG; 
    // etc. 
} 

Sembra come avere la fabbrica sa come analizzare le immagini si rompe l'incapsulamento, e lasciando che le sottoclassi decidere quale dovrebbe essere creato fa parte del modello di progettazione metodo factory. Tuttavia, sembra anche che la funzione figureOutImageType stia semplicemente aggiungendo del codice ridondante, perché perché non ogni sottoclasse esegue il proprio controllo su InputStream nella funzione getImageReader e salta il caso di commutazione?

Non ho mai avuto esperienze con le fabbriche prima e speravo di ottenere alcune informazioni da alcune persone che li hanno usati in passato sul modo migliore per gestire questo problema. Va bene che la fabbrica sia a conoscenza del funzionamento interno delle sue sottoclassi o dovrebbe essere responsabile di far sapere alla fabbrica quale creare e come organizzarlo?

Grazie!

+0

Eccellente, ben ponderata e formulata domanda. +1, di più se fosse possibile solo! – hmcclungiii

+0

concordato. Ottima domanda – Ankur

risposta

6

Factory dovrebbe avere qualche idea sulla scelta dell'oggetto reale da creare. Ad esempio, il metodo WebRequest.Create in .NET dovrebbe essere in grado di scegliere tra i diversi client di protocollo controllando la parte del protocollo di Uri. Non ha bisogno di analizzare il tutto. Solo la parte richiesta per distinguere quale classe ne sarà responsabile (nel tuo esempio, probabilmente sarà solo l'intestazione del file).

Per quanto riguarda la tua domanda sull'interruzione dell'incapsulamento, non proprio ... La maggior parte delle volte, la fabbrica è codificata e conosce già diversi tipi di classi e le loro caratteristiche. Dipende già dalla funzionalità offerta da un insieme noto di classi, quindi non ne aggiungi molto. È inoltre possibile incapsulare la parte di rilevamento della fabbrica in un'altra classe di supporto che può essere utilizzata sia dalla fabbrica che dalle sottoclassi (nello spirito del principio DRY).

+0

Grazie per l'input. Quindi potresti raccomandare di utilizzare una classe helper/utility condivisa per identificare il tipo di file da InputStream nella funzione getImageReader e semplicemente attivare l'enum restituito? – Venesectrix

+0

Sì, questo è un buon approccio. –

0

Avrei un metodo statico CanReadFrom nell'interfaccia comune ImageReader (non sicuro se questo è possibile - FIXME). Usa la riflessione per afferrare tutti gli implementatori e chiamare la funzione. Se uno restituisce true, restituisce un'istanza della classe.

1

Per l'estensibilità, è possibile esternalizzare alcune di queste dipendenze citate. Come capire che tipo di file è o mappare il tipo di file in una classe che lo gestisce. Un registro esterno (vale a dire, file delle proprietà) dovrebbe memorizzare GIF -> GifReader o GIF migliore -> GifMetadataClass. Quindi il tuo codice potrebbe essere generico e non avere dipendenze su tutte le classi, in più potresti estenderlo in futuro, oppure le terze parti potrebbero estenderlo.

+0

Penso che stai dicendo che potrei usare una mappa per associare un tipo di file con una classe che gestisce quel tipo di file, giusto? Come potrebbe la fabbrica determinare quale tipo di file era basato su InputStream in questo caso? Non sono sicuro che potresti configurare una mappa da InputStream al tipo di file. – Venesectrix

+0

Dal flusso di input, molte di queste classi di immagini hanno numeri magici all'inizio, o almeno qualcosa di generico da passare per estrarre il tipo di immagine. –

+0

È vero. Vedo cosa stai dicendo di renderlo generico ed estensibile usando le associazioni, che è sicuramente qualcosa da considerare. Nel mio caso specifico potrebbe non valerne la pena, perché ho creato solo pochi oggetti diversi e non mi aspetto di aggiungere molto altro. – Venesectrix

1

Se questo è per Windows, proverei a indovinare il tipo di contenuto e quindi utilizzare la fabbrica. In effetti l'ho fatto qualche tempo fa.

Ecco una classe di indovinare il tipo di contenuto di un file:

using System; 
using System.IO; 
using System.Runtime.InteropServices; 

namespace Nexum.Abor.Common 
{ 
    /// <summary> 
    /// This will work only on windows 
    /// </summary> 
    public class MimeTypeFinder 
    { 
     [DllImport(@"urlmon.dll", CharSet = CharSet.Auto)] 
     private extern static UInt32 FindMimeFromData(
      UInt32 pBC, 
      [MarshalAs(UnmanagedType.LPStr)] String pwzUrl, 
      [MarshalAs(UnmanagedType.LPArray)] byte[] pBuffer, 
      UInt32 cbSize, 
      [MarshalAs(UnmanagedType.LPStr)]String pwzMimeProposed, 
      UInt32 dwMimeFlags, 
      out UInt32 ppwzMimeOut, 
      UInt32 dwReserverd 
     ); 

     public string getMimeFromFile(string filename) 
     { 
      if (!File.Exists(filename)) 
       throw new FileNotFoundException(filename + " not found"); 

      var buffer = new byte[256]; 
      using (var fs = new FileStream(filename, FileMode.Open)) 
      { 
       if (fs.Length >= 256) 
        fs.Read(buffer, 0, 256); 
       else 
        fs.Read(buffer, 0, (int)fs.Length); 
      } 
      try 
      { 
       UInt32 mimetype; 
       FindMimeFromData(0, null, buffer, 256, null, 0, out mimetype, 0); 
       var mimeTypePtr = new IntPtr(mimetype); 
       var mime = Marshal.PtrToStringUni(mimeTypePtr); 
       Marshal.FreeCoTaskMem(mimeTypePtr); 
       return mime; 
      } 
      catch (Exception) 
      { 
       return "unknown/unknown"; 
      } 
     } 
    } 
} 
+0

Hanno delle funzioni di ricerca MIME in .Net o l'API di Windows chiama l'unica soluzione a corto di memorizzare la propria tabella di ricerca. – James

+0

Per quanto ne so non esiste un rilevamento di tipo mime in .NET –

2

Entrambi sono scelte valide a seconda del contesto.

SE si sta architettando per l'estensibilità, ad esempio un modello di plugin per diversi ImageReader, quindi la classe Factory non può conoscere tutti i possibili ImageReader. In tal caso, si passa alla route ImageReader.CanRead(ImageStream) - chiedendo a ciascun implementatore finché non ne trova uno in grado di leggerlo.

Attenzione, a volte l'ordine conta qui. Potresti avere un GenericImageReader in grado di gestire JPG, ma un Jpeg2000ImageReader che è meglio. Camminando, gli implementatori di ImageReader si fermeranno a chi è il primo. Potresti voler ordinare l'elenco dei possibili ImageReader se questo è un problema.

In caso contrario, se l'elenco di ImageReaders è finito e sotto il vostro controllo, è possibile passare all'approccio Factory più tradizionale. In quel caso, la Fabbrica decide cosa creare. È già abbinato alle implementazioni concrete di ImageReader da parte del ctor, quindi l'aggiunta di regole per ogni ImageReader non aumenta l'accoppiamento. Se la logica per scegliere un ImageReader è principalmente nell'ImageReader stesso, per evitare la duplicazione del codice, è comunque possibile utilizzare il percorso ImageReader.CanRead(ImageStream), ma potrebbe essere solo codificato in base al tipo di utente che si cammina.

+0

Grazie per la risposta! In realtà sto usando C++ e credo di avere solo la possibilità di camminare manualmente con i tipi ImageReader. Non conosco un modo per determinare quali sottoclassi ci sono per una classe base tranne per tenerle traccia specificatamente nel codice (hardcoded o aggiunto a un array, ecc.) – Venesectrix

Problemi correlati