2012-02-21 17 views
5

tengo una página llamada "ReportController.aspx", cuyo propósito es crear una instancia de un informe (clase) en base a los parámetros de cadena de consulta¿Reemplazo para interruptor 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; 
       ... 

Básicamente, cada vez que se necesita un nuevo informe que habrá de añadir esta sobrecarga un caso y agregando un método. Esta declaración de cambio podría ser muy larga. Leí que es posible usar un diccionario para mapear un informe a?. ¿Cómo se vería esto usando un diccionario (suponiendo que esta sea una mejor manera).

Además, el método CreateReportXReport pasa básicamente un grupo de valores adicionales de QueryString al constructor de la clase de informe (cada clase de informe tiene un constructor diferente).

+3

podría considerar el uso de una base de datos para almacenar los valores y escribir una consulta que obtenga lo que necesita. Entonces, todo lo que tiene que hacer es insertar datos con cada nuevo informe. – northpole

+0

@northpole - No estoy seguro si entiendo. Los valores se basan en las selecciones del usuario de las páginas que llamaron a ReportController. –

+0

se refieren a la respuesta de Patrick Karcher. Básicamente, aún necesita administrar la selección del usuario, sin embargo, coloca la asignación en la selección en la base de datos. Entonces tienes un método que responde a un tipo. El método recopila lo que necesita del DB (qué tipo de informe es y qué se debe generar ese informe) y crea el informe en consecuencia. – northpole

Respuesta

4

Suponiendo que todos los informes implementan IReport, puede hacerlo utilizando Func<IReport>, así:

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

A continuación, puede reemplazar el interruptor con este código:

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

Entonces todavía tendría que agregar un artículo al diccionario para cada nuevo informe; conceptualmente es lo mismo. – BrokenGlass

+1

@BrokenGlass No es realmente lo mismo en términos de mantenimiento, porque puede reutilizar el mismo 'IDictionary' tantas veces como desee sin copiar y pegar el código. No puede hacer lo mismo con la instrucción 'switch'. Puede aislar el conmutador en un método que comparta, pero no es tan flexible. Sin embargo, estoy de acuerdo en que esta no es una solución de reparación para fábricas. – dasblinkenlight

+0

@dasblinkenlight - ¿Estás insinuando que un patrón de método de fábrica sería incluso mejor que esto? –

5

No hay manera alrededor de tener escribir la nueva información en alguna parte; la clave es sacarlo del código, para evitar recompilar y volver a implementarlo para un cambio tan trivial.

Algunas buenas opciones son enumerar estos valores en un archivo de configuración XML, o mejor aún, en su base de datos.

Es probable que desee completar un diccionario con esta información, cualquiera que sea la fuente. Esta voluntad:

  • Que sea fácil de almacenar en caché
  • Hacer para la limpieza, código rápido

Cuando llega el momento de sacar sus datos de configuración en código, que le agregan elementos al diccionario de este modo:

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

este ejemplo se supone que consiguen sus datos como entidad objeto de algún tipo, y el uso de una fábrica que utilizaría un strategy pattern para su código que crea repor ts. Hay un montón de maneras en que podrías estar haciendo esto por supuesto.

Supongo que los informes son demasiado extensos, variados y de naturaleza diferente que no se puede simplemente poner sql y diseñar bloque de construcción en la base de datos?

Editar basado en los comentarios de OP:

Ah, Gotcha. Bueno, no sé cuánto tiempo tienes, pero por mucho que introduzcas todo en algún tipo de factory, tienes mejores opciones que tendrás más adelante. Voy a darle algunos pensamientos que con suerte ayudarán, de cosas similares que hice. Cada paso es una mejora en sí mismo, pero también un pequeño paso para realmente separar su lógica de informes de este código de shell. Además, puedo ver que ya sabes lo que estás haciendo y estoy seguro de que sé algo de lo que voy a decir a continuación, pero no sé lo que sabes, y será útil para otros.

Primero, extraiga toda la información del código a db (si no lo ha hecho), y agregará más campos de base de datos (y una tabla o dos) a medida que mejore su configuración.

Puede que ya lo sepas, pero lo mencionaré para otros, para ver el patrón de estrategia que menciono arriba. Puede hacer que la lógica personalizada de cada "función de informe" esté realmente en el constructor de sus diversas clases de estrategia. Todos heredarían de su ReportGenerator base (o lucirían una interfaz IReportGenerator común). Pueden y deben compartir el mismo constructor; los parámetros de informe variables se manejarían mediante un parámetro de tipo diccionario. La implementación del constructor de cada clase sabría los tipos de las variables que se necesitan (desde la configuración de db), y las usaría/usaría en consecuencia.

El siguiente paso podría ser deshacerse de su declaración seleccionada en su fábrica, usando reflection. Debería tener el nombre de la clase como parte de los datos de configuración de informes en el archivo db (y tener un constructor común).

En este punto, la forma de agregar un nuevo informe es bastante clara, aunque debe agregar una nueva clase cada vez. Asi de bueno. Cumple con los principios single responsibility y open-closed.

Ahora, solo queda el paso final de que elimina las clases de su aplicación, por lo que se pueden agregar o editar sobre la marcha. Consulte MEF. Esto es para lo que está hecho. Algunas cosas que puede encontrar en Internet que probablemente no deberían ser son CodeDom (excelente cuando no había nada más, pero MEF es mejor) y las características de compilación como servicio que vienen en .NET 5. MEF es el camino a seguir.

+0

Derecha. Para Java, esta es la razón por la que verás cosas como Spring Framework. De esta forma, solo puede hacer referencia a un objeto o diccionario común de fábrica. Su código solo llama a ReportFactory.create (Request.QueryString ["Report"]), y la lógica se mantiene fuera de su clase, lo que lo hace menos complejo. –

+0

@PatrickKarcher - Cada clase de informe toma una lista de parámetros en su constructor (no se muestra arriba). Todavía tendría que implementar el nuevo archivo (informe) en el servidor, los parámetros y los nombres de los informes son solo una parte. Hay una nueva clase completamente involucrada. –

+0

@PatrickKarcher - Tal vez en el futuro lo implementaré de esta manera, pero aún no entiendo cómo. –

1

Creo que es mejor volver a diseñar este código y convertirlo en una tabla de base de datos ("Informes") para mantener allí la lista de informes y el ID de cada informe.

Eso es todo.

+0

Sí, podría hacerlo, pero aún tendría que activar el informe después de leerlo de la base de datos, para pasar las selecciones de usuario apropiadas de la cadena de consulta a la clase de informe. No veo cómo esto sería mejor. –

+1

@ subt13 Está bien totalmente. Puede intentar usar Session/Cache para mantener allí cualquier diccionario que necesite. Así que agregue al código de la sesión este Diccionario con una lista de informes una sola vez. –

+0

Probablemente tengas razón, supongo que aún no entiendo lo suficiente como para implementarlo. –

1

Para hacer esto con un Dictionary<string, string> sólo tendría que construir uno como un caché estática en el tipo que contiene

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 ... 
    } 
} 

Ahora que el mapa se construye sólo tiene que hacer una búsqueda en la función en lugar de una 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 ¿Es esto un "patrón de método de fábrica"? –