2009-12-16 13 views
6

No estoy seguro de si hay un patrón que se debe utilizar aquí, pero aquí es la situación:Diseño pregunta patrón para el mantenimiento

Tengo un número de clases concretas que implementan una interfaz:

public interface IPerformAction 
{ 
    bool ShouldPerformAction(); 
    void PerformAction(); 
} 

Tengo otra clase que verifica la entrada para determinar si ShouldPerformAction se debe ejecutar. El problema es que los nuevos controles se agregan con bastante frecuencia. La interfaz para la clase de verificación se define como sigue:

public interface IShouldPerformActionChecker 
{ 
    bool CheckA(string a); 
    bool CheckB(string b); 
    bool CheckC(int c); 
    // etc... 
} 

Finalmente tengo actualmente las clases concretas llamar a cada uno de los métodos de corrector con los datos específicos de esa clase concreta:

public class ConcreteClass : IPerformAction 
{ 
    public IShouldPerformActionCheck ShouldPerformActionChecker { get; set; } 

    public string Property1 { get; set; } 
    public string Property2 { get; set; } 
    public int Property3 { get; set; } 

    public bool ShouldPerformAction() 
    { 
     return 
     ShouldPerformActionChecker.CheckA(this.Property1) || 
     ShouldPerformActionChecker.CheckB(this.Property2) || 
     ShouldPerformActionChecker.CheckC(this.Property3); 
    } 

    public void PerformAction() 
    { 
     // do something class specific 
    } 
} 

Ahora cada vez Agrego un nuevo cheque, tengo que refactorizar las clases concretas para incluir el nuevo cheque. Cada clase concreta pasa diferentes propiedades al método de comprobación, de modo que las subclases de las clases concretas no son una opción. ¿Alguna idea sobre cómo esto podría implementarse de una manera más limpia?

+0

personalmente creo que el método actual es bastante limpio. ¿Cuántas clases concretas estamos hablando? ¿Es realmente tan difícil modificar las clases concretas necesarias a medida que aparecen nuevos controles? Creo que no importa cómo lo elimines, todavía vas a terminar con una clase concreta que tienes que modificar. La única forma de evitar esto es hacer una función de estilo "CheckAll()" en algún lugar que usen las clases concretas. Sin embargo, en general, creo que el mayor rendimiento de no tener que ajustar no va a superar el factor de barro. – mike

Respuesta

2

El "checka", "CheckB", etc. nombres, presumiblemente elegidos para evitar la exposición de información confidencial, también ofuscar la naturaleza de las relaciones entre las clases, por lo que voy a tener que improvisar.

Sin embargo, esto es casi double dispatch, excepto que está realizando la conversión de los objetos en el medio.

EDITAR: Intente reproducir el patrón de despacho doble "por el libro", en lugar de descomponer los objetos a mitad de despacho. Para ello, se necesitaría algo como lo siguiente:

public interface IPerformAction 
{ 
    bool ShouldPerformAction(IShouldPerformActionChecker checker); 
    void PerformAction(); 
} 

public interface IShouldPerformActionChecker 
{ 
    bool CheckShouldPerformAction(FloorWax toCheck); 
    bool CheckShouldPerformAction(DessertTopping toCheck); 
    // etc... 
} 

public class FloorWax : IPerformAction 
{ 
    public string Fragrance { get; set; } 

    // Note that the text of this method is identical in each concrete class, 
    // but compiles to call a different overload of CheckShouldPerformAction. 
    public bool ShouldPerformAction(IShouldPerformActionChecker checker) 
    { 
     return checker.CheckShouldPerformAction(this); 
    } 
} 

public class DessertTopping: IPerformAction 
{ 
    public string Flavor { get; set; } 

    // Note that the text of this method is identical in each concrete class, 
    // but compiles to call a different overload of CheckShouldPerformAction. 
    public bool ShouldPerformAction(IShouldPerformActionChecker checker) 
    { 
     return checker.CheckShouldPerformAction(this); 
    } 
} 

public class ShimmerApplicationChecker : IShouldPerformActionChecker 
{ 
    // handles binding of FloorWax class to required checks 
    public bool CheckShouldPerformAction(FloorWax toCheck) 
    { 
     return CheckAroma(toCheck.Fragrance); 
    } 

    // handles binding of DessertTopping class to required checks 
    public bool CheckShouldPerformAction(DessertTopping toCheck); 
    { 
     return CheckAroma(toCheck.Flavor); 
    } 

    // some concrete check 
    private bool CheckAroma(string aroma) 
    { 
     return aroma.Contains("chocolate"); 
    } 
} 
+0

Gracias Jeffey. Gran explicación – bmancini

0

Cuando cree un nuevo CheckN, tendrá que implementarlo en cada una de sus clases de comprobador de concreto de todos modos, ¿no?

¿O está hablando de refactorizar su IPerformActions para llamar realmente ese cheque?

¿Por qué no tiene un CheckAll que lo llama todo?

+0

Cada llamada a CheckN tendrá datos específicos para la clase concreta que la está llamando. Un método CheckAll podría funcionar, pero todavía requeriría que la clase concreta le aplicara sus datos específicos. Cuanto más lo pienso, más pienso que realmente no hay forma de evitar tener que entrar en cada clase concreta para cada adición. Supongo que una alternativa sería una ShouldPerformActionCheckerFactory y la lógica específica de la clase concreta se aplica allí. No estoy seguro de si eso solo enturbia el agua. – bmancini

+0

Si tiene que tomar una decisión específica de la clase sobre qué datos se pasan a cada método, entonces deberá ajustar cada clase cuando agregue otro cheque, sin importar de qué lado lo corte. –

+0

No necesariamente. Ver mi descripción de doble envío. –

1

Usted puede consolidar los cheques en un solo método general que toma un objeto:

public interface IShouldPerformActionChecker 
{ 
    bool Check(object o); 
} 

luego tener una lista de estos controles en su clase concreta:

public List<IShouldPerformActionCheck> ShouldPerformActionChecker { get; set; } 

Menos de tipo seguro, pero mas flexible

En lugar de usar IShouldPerformActionCheck, puede usar el delegado Predicate<T>, que hace más o menos lo mismo.

3

Demos un paso atrás, ¿por qué está utilizando interfaces en primer lugar? ¿Puede una implementación única de IShouldPerformActionCheck ser compartida entre múltiples implementaciones de IPerformAction? Parece que la respuesta es no, ya que ICheck debe conocer las propiedades específicas de la implementación (Propiedad1, Propiedad2, Propiedad3) en la Acción para realizar la comprobación. Por lo tanto, la relación entre IAction e ICheck requiere más información de la que el contrato de IAction puede proporcionar a ICheck. Parece sus clases Verificar deben basarse en implementaciones concretas que se acoplan con el tipo específico de acción que comprueban, como:

abstract class CheckConcreteClass 
{ 
    abstract void Check(ConcreteClass concreteInstance); 
} 
+0

Sí, se podría compartir una única implementación de IShouldPerformCheck. En realidad, realiza reglas de validación compartidas, como la verificación de que una IP no está bloqueada, un correo electrónico no está bloqueado o que las palabras clave no están bloqueadas. Todo lo que importa para IShouldPerformCheck es que la entrada sea específica para la regla de validación. Las clases concretas pueden proporcionar estas entradas de diferentes maneras. Por ejemplo, la verificación de palabras clave puede incluir varias propiedades diferentes de la clase concreta. – bmancini

0

En lugar de tienen las clases concretas tratar de comprobar si la acción debe llevarse a cabo, podría ser una forma más sostenible de organizar estos objetos.

¿Qué pasa si el verificador realmente implementó IPerformAction, y tenía un miembro IPerformAction al que llamaría si la acción se llevara a cabo? ¿Ese miembro podría ser otro inspector en la cadena o la clase real que realiza la acción si se han pasado todos los criterios?

Para hacer eso puede ser necesario que refactorice ligeramente, de modo que la lógica para realizar la acción esté contenida en una clase, mientras que los datos específicos que se utilizarán estén en otra (como un patrón de comando), de modo que las damas podrían hacer su trabajo.

De esta forma, podría agregar fácilmente otra regla de validación, simplemente colocándola en la "cadena" de objetos que conduce a la acción final.

0

Usted podría intentar algo como esto:

List<IShouldPerformActionChecker> checkers = new List<IShouldPerformActionChecker>(); 

//... add in each checker to try 

foreach(ConcreteClass objectToCheck in checkset) { 
    bool isGood = true; 
    foreach(IShouldPerformActionChecker checker in checkers) { 
     isGood &= checker.DoCheck(objectToCheck.Property); 

     if (!isGood) { break; } 
    } 

    //handle failed check here 
}