2009-12-16 7 views
6

non sono sicuro se c'è un modello che deve essere utilizzato qui, ma qui è la situazione:design domanda modello per la manutenibilità

Ho un certo numero di classi concrete che implementano un'interfaccia:

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

Ho un'altra classe che controlla l'input per determinare se ShouldPerformAction deve essere eseguito. Il problema è che i nuovi assegni vengono aggiunti abbastanza frequentemente. L'interfaccia per la classe controllo è definito come segue:

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

Infine Al momento ho le classi concrete chiamare ciascuno dei metodi controllo con i dati specifici per quella classe 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 
    } 
} 

Ora ogni volta Aggiungo un nuovo assegno, devo refactoring le lezioni concrete per includere il nuovo assegno. Ogni classe concreta trasmette proprietà diverse al metodo di controllo, quindi le sottoclassi delle classi concrete non sono un'opzione. Qualche idea su come questo potrebbe essere implementato in modo più pulito?

+0

Personalmente penso che il metodo attuale è abbastanza pulito. Di quante lezioni concrete stiamo parlando? È davvero così difficile modificare le lezioni concrete necessarie quando arrivano nuovi assegni? Penso che non importa come lo tagli, finirai per finire con un corso concreto che devi modificare. L'unico modo per evitare ciò è di eseguire una funzione di stile "CheckAll()" da qualche parte utilizzata dalle classi concrete. Nel complesso, comunque, penso che l'aumento delle prestazioni di non dover modificare non supererà il fattore di fango. – mike

risposta

2

I nomi "CheckA", "CheckB", ecc., Presumibilmente scelti per evitare di esporre informazioni riservate, offuscano anche la natura delle relazioni tra classi, quindi dovrò inserirle.

Tuttavia, questo è quasi uguale a double dispatch, tranne che si sta eseguendo la conversione degli oggetti nel mezzo.

MODIFICA: provare a riprodurre il modello di doppia spedizione "dal libro", piuttosto che scomporre gli oggetti a metà invio. Per fare questo, avreste bisogno di qualcosa di simile al seguente:

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

Grazie Jeffey. Ottima spiegazione. – bmancini

0

Quando si crea un nuovo CheckN, sarà necessario implementarlo in ciascuna delle vostre classi di correttori concreti comunque, no?

Oppure stai parlando di refactoring delle tue IPerformActions per chiamare effettivamente quell'assegno?

Perché non hai appena un CheckAll che chiama tutto?

+0

Ogni chiamata a CheckN avrà dati specifici per la classe concreta che sta chiamando. Un metodo CheckAll potrebbe funzionare, ma richiederebbe comunque alla classe concreta di applicare i suoi dati specifici. Più ci penso e più penso che non ci sia davvero modo di evitare di dover entrare in ogni classe concreta per ogni aggiunta. Suppongo che un'alternativa sia una ShouldPerformActionCheckerFactory e che la logica specifica della classe concreta venga applicata lì. Non sono sicuro se ciò offusca l'acqua. – bmancini

+0

Se devi prendere una decisione specifica per la classe in merito a quali dati vengono passati a quale metodo, allora dovrai modificare ogni classe quando aggiungi un altro controllo, indipendentemente dal modo in cui lo dividi. –

+0

Non necessariamente. Vedi la mia descrizione della doppia spedizione. –

1

Si potrebbe consolidare i controlli in un unico metodo generale che prende un oggetto:

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

Poi hanno un elenco di questi controlli nella classe concreta:

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

Meno type-safe, ma più flessibile.

Invece di utilizzare IShouldPerformActionCheck si potrebbe guardare usando Predicate<T> delegato, che fa grosso modo la stessa cosa.

3

Facciamo un passo indietro - perché stai usando le interfacce in primo luogo? È possibile condividere un'unica implementazione di IShouldPerformActionCheck tra più implementazioni di IPerformAction? Sembra che la risposta sia no, poiché ICheck deve essere a conoscenza delle proprietà specifiche dell'implementazione (Property1, Property2, Property3) su Action per poter eseguire il controllo. Pertanto la relazione tra IAction e ICheck richiede più informazioni di quelle che il contratto di IAction può fornire a ICheck. Sembra che il tuo Controlla i corsi dovrebbero essere basate su implementazioni concrete che sono accoppiati al tipo specifico di azione che controllano, come:

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

Sì, è possibile condividere una singola implementazione di IShouldPerformCheck. Effettua effettivamente regole di convalida condivise, come la verifica che un IP non sia bloccato, un messaggio di posta elettronica non bloccato o che le parole chiave non siano bloccate. Tutto ciò che conta per IShouldPerformCheck è che l'input è specifico per la regola di convalida. Le classi concrete possono fornire questi input in modi diversi. Ad esempio, il controllo delle parole chiave può includere diverse proprietà della classe concreta. – bmancini

0

Invece di avere le classi concrete cercare di verificare se deve essere eseguita l'azione, ci potrebbe essere un modo più manutenibile per disporre questi oggetti.

E se il correttore avesse effettivamente implementato IPerformAction e avesse un membro IPerformAction che avrebbe chiamato se l'azione dovesse essere eseguita? Quel membro potrebbe essere un altro correttore nella catena o la classe reale che esegue l'azione se tutti i criteri sono stati superati?

Per fare ciò potrebbe richiedere un leggero refactoring, in modo che la logica per eseguire l'azione sia contenuta in una classe, mentre i dati specifici da utilizzare siano in un'altra (una specie di modello Command), in modo che i dama potrebbero fare il loro lavoro.

In questo modo, è possibile aggiungere facilmente un'altra regola di convalida, semplicemente inserendola nella "catena" di oggetti che conducono all'azione finale.

0

Si potrebbe provare qualcosa di simile:

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 
}