2009-12-16 18 views
6

Ich bin nicht sicher, ob es ein Muster ist, die hier verwendet werden soll, aber hier ist die Situation:Design-Muster Frage für Wartbarkeit

ich eine Reihe von konkreten Klassen, die eine Schnittstelle implementieren:

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

Ich habe eine andere Klasse, die die Eingabe überprüft, um festzustellen, ob ShouldPerformAction ausgeführt werden soll. Der Haken ist, dass neue Prüfungen ziemlich häufig hinzugefügt werden. Die Schnittstelle für die Prüfung Klasse wird wie folgt definiert:

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

Endlich habe ich zur Zeit die konkreten Klassen jeder der Checker-Methoden aufrufen mit den Daten spezifisch für diese konkrete Klasse:

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

Jetzt jedes Mal, Ich füge einen neuen Check hinzu, ich muss die konkreten Klassen umgestalten, um den neuen Check aufzunehmen. Jede konkrete Klasse übergibt der Prüfmethode andere Eigenschaften, so dass Unterklassen der konkreten Klassen keine Option sind. Irgendwelche Ideen, wie dies sauberer umgesetzt werden könnte?

+0

Ich persönlich denke, Ihre aktuelle Methode ziemlich sauber ist. Wie viele konkrete Klassen reden wir? Ist es wirklich so schwierig, die notwendigen konkreten Klassen zu optimieren, wenn neue Prüfungen eintreffen? Ich denke, es spielt keine Rolle, wie du es zerschneidest, du wirst immer noch mit einer konkreten Klasse enden, die du anpassen musst. Die einzige Möglichkeit, dies zu vermeiden, ist eine "CheckAll()" -Stilfunktion, die irgendwo in den konkreten Klassen verwendet wird. Insgesamt denke ich, dass die erhöhte Leistung, nicht zwicken zu müssen, den Schlammfaktor nicht überwiegen wird. – mike

Antwort

2

Die Namen "CheckA", "CheckB" usw., die vermutlich dazu verwendet wurden, vertrauliche Informationen nicht zu offenbaren, verschleiern auch die Art der Beziehungen zwischen den Klassen, also muss ich sie weiterverfolgen.

Dies ist jedoch fast double dispatch, außer dass Sie die Konvertierung der Objekte dazwischen durchführen.

EDIT: Versuchen Sie, das doppelte Versandmuster "nach dem Buch" zu spielen, anstatt die Objekte in der Mitte des Versands zu zerlegen. Um dies zu tun, würden Sie so etwas wie die folgenden Voraussetzungen:

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

Danke Jeffey. Tolle Erklärung. – bmancini

0

Wenn Sie eine neue CheckN erstellen, müssen Sie sie in jeder Ihrer konkreten Checker-Klassen implementieren, nein?

Oder referieren Sie über Refactoring Ihrer IPerformActions, um diesen Check tatsächlich aufzurufen?

Warum haben Sie nicht einfach einen CheckAll, der alles anruft?

+0

Jeder Aufruf von CheckN enthält Daten, die für die konkrete Klasse, die sie aufruft, spezifisch sind. Eine CheckAll-Methode könnte funktionieren, es würde jedoch weiterhin erforderlich sein, dass die konkrete Klasse ihre spezifischen Daten darauf anwendet. Je mehr ich darüber nachdenke, desto mehr denke ich, dass es wirklich keine Möglichkeit gibt zu verhindern, dass man für jede Addition in jede konkrete Klasse gehen muss. Ich denke, eine Alternative wäre eine ShouldPerformActionCheckerFactory und die konkrete klassenspezifische Logik wird dort angewendet. Ich bin mir nicht sicher, ob das nur das Wasser matschig macht. – bmancini

+0

Wenn Sie eine klassenspezifische Entscheidung treffen müssen, welche Daten an welche Methode übergeben werden, müssen Sie jede Klasse anpassen, wenn Sie eine weitere Überprüfung hinzufügen, unabhängig davon, in welcher Weise Sie sie schneiden. –

+0

Nicht unbedingt. Siehe meine Beschreibung des Doppelversands. –

1

Sie könnten die Kontrollen in eine einzige allgemeine Methode konsolidieren, die ein Objekt nimmt:

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

Dann hat eine Liste dieser Prüfungen in Ihrer konkreten Klasse:

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

Weniger typsicher, aber flexibler.

Anstatt IShouldPerformActionCheck zu verwenden, könnte man sich auch mit Predicate<T> Delegaten beschäftigen, was in etwa der gleichen Sache entspricht.

3

Lassen Sie uns einen Schritt zurück gehen - warum verwenden Sie überhaupt Schnittstellen? Kann eine einzelne Implementierung von IShouldPerformActionCheck zwischen mehreren Implementierungen von IPerformAction geteilt werden? Es scheint, dass die Antwort nein ist, da ICheck die implementierungsspezifischen Eigenschaften (Property1, Property2, Property3) der Aktion kennen muss, um die Prüfung durchzuführen. Daher erfordert die Beziehung zwischen IAction und ICheck mehr Informationen, als der IAction-Vertrag ICheck zur Verfügung stellen kann. Es scheint, Ihre Preisklassen sollten sich auf konkrete Implementierungen basieren, die auf die spezifische Art der Maßnahmen gekoppelt werden sie prüfen, wie:

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

Ja, eine einzelne Implementierung von IShouldPerformCheck könnte geteilt werden. Es führt tatsächlich gemeinsame Validierungsregeln aus, z. B. um zu überprüfen, dass eine IP nicht blockiert ist, eine E-Mail nicht blockiert ist oder dass Schlüsselwörter nicht blockiert sind. Für IShouldPerformCheck ist nur wichtig, dass die Eingabe für die Validierungsregel spezifisch ist. Die konkreten Klassen können diese Eingaben auf verschiedene Arten bereitstellen. Zum Beispiel kann die Schlüsselwortprüfung mehrere verschiedene Eigenschaften der konkreten Klasse umfassen. – bmancini

0

Statt haben die konkreten Klassen versuchen, zu überprüfen, ob die Aktion ausgeführt werden soll, könnte es sei eine aufrechterhaltbare Möglichkeit, diese Objekte anzuordnen.

Was wäre, wenn der Prüfer tatsächlich IPerformAction implementiert und ein IPerformAction-Mitglied hätte, das er aufrufen würde, wenn die Aktion ausgeführt werden soll? Dieses Mitglied könnte entweder ein anderer Prüfer in der Kette oder die tatsächliche Klasse sein, die die Aktion ausführt, wenn alle Kriterien bestanden wurden?

Um dies zu tun, müssen Sie möglicherweise leicht refactoring, so dass die Logik, um die Aktion durchzuführen, in einer Klasse enthalten ist, während die spezifischen Daten in einem anderen (Art von einem Befehlsmuster) verwendet werden Die Dame könnte ihre Arbeit machen.

Auf diese Weise können Sie leicht eine weitere Validierungsregel hinzufügen, indem Sie sie einfach in die "Kette" der Objekte einfügen, die zur letzten Aktion führen.

0

Sie könnten so etwas wie dies versuchen:

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 
}