2011-12-21 10 views
5

Probablemente esta pregunta se haya publicado anteriormente, pero no pude encontrarla.Declaración de interfaz o interruptor, encontrar el patrón correcto

He estado escribiendo este tipo de cosas durante tanto tiempo, me siento a escribir algo nuevo y simplemente empiezo a escribir esto como si fuera mi propio patrón. Hace poco surgió un proyecto y me encontré mirando mi propio código y comencé a pensar en lo mal que se ve.

BackgroundInfoIfYouCare 

En esta biblioteca en particular tengo que enviar correos electrónicos a los usuarios. Hasta ahora hay 13 correos electrónicos enlatados.

Cada correo electrónico tiene su propia plantilla (estoy usando un analizador Razor, por lo que las plantillas están escritas en cshtml). Cada plantilla de correo electrónico tiene una clave de nombre de cadena. Cada correo electrónico tiene su propia consulta EF4 para devolver un modelo basado en una entidad de "membresía" y todos los datos relacionados.

Tengo una clase que acepta una cadena que es una clave de nombre de plantilla de correo electrónico.

El método ejecutará la consulta adecuada y obtendrá una lista de regreso, agarra la plantilla de correo electrónico.

La lista y la plantilla se pasan a un analizador para fusionar cada una de las membresías a la plantilla y devuelve una lista de correos electrónicos.

EndOfBackgroundInfoIfYouCare 

Entonces la verdadera pregunta ... ¿cuál es la mejor manera de hacer esto?

Una forma es utilizar sólo un interruptor de

public List<Membership> Execute(string TemplateKey) { 
switch (TemplateKey) 
     { 
      case "SomethingExpired": 
       QueryResult = new SomethingExpiredEmailQuery().ExecuteQuery(); 
       break; 
      case "SomethingExpireIn30": 
       QueryResult = new SomethingExpireIn30EmailQuery().ExecuteQuery(); 
       break; 
      case "FirstTimeLoginThanks": 
       QueryResult = new FirstTimeLoginThanksEmailQuery().ExecuteQuery(); 
       break; 
      case "SecurityTraining": 
       QueryResult = new SecurityTrainingEmailQuery().ExecuteQuery(); 
       break; 
      case ETC ETC ETC... 

}

Otra manera sería utilizar una interfaz

IEmailQuery 
void ExecuteQuery() 

Pero si uso una interfaz todavía necesitarán crear una instancia de la clase Query. No guarda código y no hace que el código sea más fácil de mantener.

Con la reflexión que podía hacer algo así como el nombre de todas las consultas de correo electrónico con un patrón: Plantilla Email Clave de SecurityTraining tiene un nombre de consulta de SecurityTrainingEmailQuery y pude utilizar para crear instancias de reflexión y llame al método ExecuteQuery.

Sin usar reflexión, ¿no hay una manera más limpia de cablear esto?

Respuesta

3

En realidad, esto no me parece mal. Si no te gusta la declaración de cambio, puedes ir a IEmailQuery-Path y simplemente conectarlo en un Dictionary<string,IEmailQuery>. Esto probablemente guarda algunas líneas de código, ya que se podía acceder a él de esa manera:

QueryDictionary["MyKey"].ExecuteQuery(); 

Cheers, Oliver

+0

Bueno, la respuesta de Jon es similar, solo que mucho más avanzada que la mía, me temo ;-) – Lindan

7

Una opción es tener un mapa de Dictionary<string, Func<IEmailQuery>>. Se puede construir de esta manera:

private static readonly Dictionary<string, Func<IEmailQuery>> MailQueryMap = 
    new Dictionary<string, Func<IEmailQuery>> { 
    { "SomethingExpired",() => new SomethingExpiredMailQuery() }, 
    { "SomethingExpireIn30",() => new SomethingExpireIn30EmailQuery() }, 
    // etc 
}; 

continuación:

public List<Membership> Execute(string templateKey) { 
    IEmailQuery query = MailQueryMap[templateKey].Invoke(); 
    var queryResult = query.ExecuteQuery(); 
    // ... 
} 

Si puede garantía que sólo se necesita siempre constructores sin parámetros, siempre se puede almacenar un Dictionary<string, Type> y instanciarlo través de la reflexión - pero Habrá algunos moldes feos, etc.

EDIT: Por supuesto, si el nombre de la plantilla siempre es el nombre del tipo, se puede usar

Type queryType = Type.GetType(namespacePrefix + "." + templateKey); 
IEmailQuery query = (IEmailQuery) Activator.CreateInstance(queryType); 
var queryResult = query.ExecuteQuery(); 

También puede querer consideran utilizando una enumeración en lugar de la magia constantes de cuerda

+0

¿Cómo afecta esto sigue el principio abierto/cerrado? Si leo la publicación de Paul correctamente, quiere evitar tener que cambiar las clases existentes (ampliando la declaración de cambio con casos nuevos). – Wivani

+0

@Wivani: No vi nada que sugiriera eso: solo vi que él quiere que el código sea más simple y fácil de mantener. ¿En qué parte de la pregunta se habla de evitar tener que cambiar las clases existentes? –

+0

Supongo que me estoy sacando de quicio ;-) Veamos si Paul puede confirmar lo que creo que está buscando. – Wivani

0

¿Por qué no utilizar el reflejo como lo propuso en su pregunta? Creo que es una forma válida de hacer este tipo de cosas.

Otro enfoque sería utilizar la inversión del patrón de inyección de control/dependencia. Usted define una interfaz como lo hizo y registra todas las implementaciones concretas conocidas en su contenedor DI (esto puede hacerse por configuración o por código).

Al registrarse, debe indicar al contenedor DI un nombre de servicio para distinguir las implementaciones, ya que implementan la misma interfaz.

YourIocContainer.Register<IEmailQuery>(typeof(SomethingExpiredMailQuery), 
             "SomethingExpiredMailQuery"); 

Cuando instanciar, se puede obtener la aplicación correspondiente al suministrar el nombre del servicio nuevo:

public List<Membership> Execute(string TemplateKey) { 
    YourIocContainer.Resolve<IEmailQuery>(TemplateKey); 
+0

Cuando estaba pensando en hacer la pregunta sobre stackoverflow, lo primero que pensé fue en la reflexión.La velocidad no es un problema, por lo que sería un método válido de usar. Solo tenía curiosidad por saber qué otras soluciones podrían estar por ahí en las que no había pensado. Me gusta su solución de inyección de dependencia también. –

1

yo iría a un patrón de fábrica, algo así como

class EmailQueryFactory 
{ 
    public IEmailQuery Create(String TemplateKey) 
    { 
    .... 
    } 
} 

y luego

//.. first get String TemplateKey 

IEmailQuery qry=EmailQueryFactory.Create(TemplateKey); 
qry.Execute(); 
+0

Estaba pensando en usar una fábrica, pero luego en la fábrica terminas con el mismo problema. Necesita cablear la plantilla solicitada a la clase de consulta adecuada. –

0

El patrón de comando es un patrón perfecto para este escenario. Vea http://www.codeproject.com/KB/books/DesignPatterns.aspx para una descripción práctica C# de este patrón. Lambdas como Jon Skeet ha descrito son constructos de programación más nuevos y útiles que puedes ver. Consulte Command Pattern : How to pass parameters to a command? para obtener más información sobre el uso del patrón.

+0

Gracias por mencionar el patrón de comando. Me forzó a volver a leer la definición del libro 4gang. Aunque tengo curiosidad en cuanto a lo que ganaría con su uso. ¿Todavía no necesitaría crear una instancia del receptor para invocar el comando? Me devuelve a una lista de clases y asociar un comando solicitado al correcto. –

Cuestiones relacionadas