2012-02-21 19 views
5

Ho una pagina denominata "ReportController.aspx" il cui scopo è di creare un'istanza di un report (classe) in base a parametri stringa di querySostituzione per interruttore grande?

 switch (Request.QueryString["Report"]) 
     {     
      case "ReportA": 
       CreateReportAReport("ReportA's Title"); 
       break; 
      case "ReportB": 
       CreateReportBReport("ReportB's Title"); 
       break;     
      case "ReportC": 
       CreateReportCReport("ReportC's Title"); 
       break; 
      case "ReportD": 
       CreateReportDReport("ReportD's Title"); 
       break; 
       ... 

In pratica, ogni volta che è necessario un nuovo rapporto ci sarà questo overhead di aggiunta un caso e aggiungendo un metodo. Questa dichiarazione di commutazione potrebbe essere molto lunga. Ho letto che è possibile utilizzare un dizionario per mappare un report in?. Come apparirebbe usando un dizionario (supponendo che questo sia un modo migliore).

Inoltre, il metodo CreateReportXReport passa fondamentalmente un gruppo di valori QueryString aggiuntivi al costruttore della classe del report (ogni classe di report ha un costruttore diverso).

+3

Si potrebbe considerare l'utilizzo di un database per memorizzare i valori e scrivere una query che ottenga ciò di cui si ha bisogno. Quindi tutto ciò che devi fare è inserire i dati con ogni nuovo rapporto. – northpole

+0

@northpole - Non sono sicuro se capisco. I valori sono basati sulle selezioni dell'utente dalle pagine che hanno chiamato a ReportController. –

+0

si riferiscono alla risposta di Patrick Karcher. Fondamentalmente, è ancora necessario gestire la selezione dell'utente, tuttavia, si mette la mappatura alla selezione nel database. Quindi hai un metodo che risponde a un tipo. Il metodo raccoglie ciò di cui ha bisogno dal DB (che tipo di report è e che report deve essere compilato) e crea il report di conseguenza. – northpole

risposta

4

Partendo dal presupposto che tutti i rapporti implementare IReport, è possibile farlo utilizzando Func<IReport>, in questo modo:

IDictionary<string,Func<IReport>> dictToReport = new Dictionary { 
    {"ReportA",() => CreateReportAReport("ReportA's Title") } 
, {"ReportB",() => CreateReportBReport("ReportB's Title") } 
, ... 
}; 

È quindi possibile sostituire l'interruttore con questo codice:

var myReport = dictToReport[Request.QueryString["Report"]](); 
+2

Quindi dovresti ancora aggiungere un elemento al dizionario per ogni nuovo rapporto: concettualmente è lo stesso. – BrokenGlass

+1

@BrokenGlass Non è esattamente lo stesso in termini di manutenzione, perché è possibile riutilizzare lo stesso IDictionary quante volte si desidera senza incollare il codice. Non puoi fare la stessa cosa con l'istruzione 'switch'. Puoi isolare lo switch in un metodo che condividi, ma non è così flessibile. Sono d'accordo sul fatto che questa non è una soluzione fissa per le fabbriche, però. – dasblinkenlight

+0

@dasblinkenlight - Stai suggerendo che un modello di metodo di fabbrica sarebbe anche meglio di questo? –

5

Non c'è niente da fare intorno dover digitare le nuove informazioni da qualche parte; la chiave è farla uscire dal codice, per evitare di ricompilare e ridistribuire per un cambiamento così banale.

Alcune buone opzioni sono elencare questi valori in un file di configurazione XML, o, meglio ancora, nel database.

Probabilmente vorrai compilare un dizionario con questi dati, qualunque sia la fonte. Questa volontà:

  • Lo rendono facile da memorizzare nella cache
  • rendono pulito, codice veloce

Quando arriva il momento di tirare i dati di configurazione in codice, che ci si aggiungono elementi Dizionario in questo modo:

Dictionary<string, IReportCreator> = configDataGetter.GetReportDataFromDB(). 
    ToDictionary(r => r.Name, myReportCreatorFactory(r => r.ReportID)) 

Questo esempio presuppone i dati ottenendo come oggetto entità di qualche tipo, e l'utilizzo di una fabbrica che userebbe un strategy pattern per il vostro codice che crea RELAZIONE ts. C'è un sacco di modi in cui potresti farlo naturalmente.

Immagino che i report siano troppo estesi, vari e di natura diversa che non è possibile inserire solo sql e il building block di stile nel db?

Modifica sulla base di commenti di OP:

Ah, Gotcha. Beh, non so quanto tempo hai, ma tanto quanto puoi spingere tutto in una sorta di factory, hai opzioni migliori che avrai in seguito. Ho intenzione di darti alcuni pensieri che, si spera, aiuteranno, da cose simili che ho fatto. Ogni passaggio è un miglioramento in sé, ma anche un piccolo passo per separare davvero la logica del report da questo codice shell. Inoltre, posso vedere che sai già cosa stai facendo e sono sicuro di sapere cosa dirò di seguito, ma non so cosa tu sappia e sarà utile per gli altri.

Prima di tutto, estrai qualsiasi bit di informazioni dal codice a db (se non lo hai già fatto) e aggiungerai altri campi DB (e una tabella o due) mentre migliori la tua configurazione.

Forse lo sapete già, ma lo menzionerò per altri, per verificare lo schema di strategia di cui sopra. È possibile avere la logica personalizzata di ciascuna "funzione di report" effettivamente nel costruttore delle varie classi di strategia. Verrebbero tutti ereditati dal proprio ReportGenerator di base (o dotati di un'interfaccia IReportGenerator comune). Possono e dovrebbero condividere lo stesso costruttore; i vari parametri del rapporto verrebbero gestiti da un parametro del dizionario dei tipi. L'implementazione del costruttore di ogni classe dovrebbe sapere che i tipi di variabili sono necessari (dalla configurazione di db) e li casterebbe/li userà di conseguenza.

Il passaggio successivo potrebbe essere quello di eliminare davvero l'istruzione selezionata in fabbrica, utilizzando reflection. Dovresti avere il nome della classe come parte dei dati di configurazione dei report nel db (e avere un costruttore comune).

A questo punto, il modo per aggiungere un nuovo report è abbastanza pulito, anche se devi aggiungere una nuova classe ogni volta. Bene. Soddisfa i principi single responsibility e open-closed.

Ora, c'è solo il passaggio finale di rimuovere le classi dall'app, in modo che possano essere aggiunte/modificate al volo. Controlla MEF. Questo è ciò per cui è fatto. Alcune cose che potresti trovare su Internet che probabilmente non dovresti usare sono CodeDom (ottimo quando non c'era nient'altro, ma MEF è meglio) e le funzioni di compilazione-come-servizio che arrivano in .NET 5. MEF è la strada da percorrere

+0

Giusto. Per Java, questo è il motivo per cui vedrai cose come Spring Framework. In questo modo puoi semplicemente fare riferimento a un oggetto o dizionario di fabbrica comune. Il tuo codice chiama semplicemente ReportFactory.create (Request.QueryString ["Report"]), e la logica viene mantenuta al di fuori della tua classe, rendendola meno complessa. –

+0

@PatrickKarcher - Ogni classe del report contiene un elenco di parametri nel suo costruttore (non mostrato sopra). Devo ancora distribuire il nuovo file (report) sul server, i parametri e i nomi dei report sono solo una parte di esso. C'è un'intera nuova classe coinvolta. –

+0

@PatrickKarcher - Forse in futuro implementerò in questo modo, ma non capisco ancora. –

1

Penso che sia meglio riprogettare questo codice e convertirlo in qualche tabella di database ("Rapporti") per conservare l'elenco di rapporti e ID di ogni rapporto.

Questo è tutto.

+0

Sì, potrei farlo, ma dovrei comunque accendere il rapporto dopo averlo letto dal database, per passare le selezioni utente appropriate dalla querystring alla classe del report. Non vedo come sarebbe meglio. –

+1

@ subt13 Va tutto bene. Puoi provare a utilizzare Session/Cache per conservare qualsiasi dizionario di cui hai bisogno. Quindi aggiungi al codice di sessione questo dizionario con una lista di rapporti una sola volta. –

+0

Probabilmente hai ragione, suppongo di non capire abbastanza per implementarlo. –

1

di fare questo con un Dictionary<string, string> si dovrebbe semplicemente costruire uno come una cache statica nel tipo contenente

public class Container { 
    private static Dictionary<string, Func<Report>> ReportMap = 
    new Dictionary<string, Func<Report>>(); 
    static Container() { 
    ReportMap["ReportA"] =() => CreateReportAReport("ReportA's Title"); 
    ReportMap["ReportB"] =() => CreateReportBReport("ReportB's Title"); 
    // etc ... 
    } 
} 

Ora che la mappa è costruita è sufficiente fare una ricerca nella funzione invece di un switch

Func<Report> func; 
if (!ReportMap.TryGetValue(Request.QueryString["Report"), out func)) { 
    // Handle it not being present 
    throw new Exception(..); 
} 

Report report = func(); 
+0

+1 È un "modello di metodo di fabbrica"? –