it-swarm.com.de

Wie kann eine Anwendung mit mehreren Switch-Fällen umgestaltet werden?

Ich habe eine Anwendung, die eine Ganzzahl als Eingabe verwendet und basierend auf der Eingabe statische Methoden verschiedener Klassen aufruft. Jedes Mal, wenn eine neue Nummer hinzugefügt wird, müssen wir einen weiteren Fall hinzufügen und eine andere statische Methode einer anderen Klasse aufrufen. Es gibt jetzt 50 Fälle im Schalter und jedes Mal, wenn ich einen weiteren Fall hinzufügen muss, schaudere ich. Gibt es einen besseren Weg, dies zu tun.

Ich habe nachgedacht und bin auf diese Idee gekommen. Ich benutze das Strategiemuster. Anstatt einen Schalterfall zu haben, habe ich eine Karte von Strategieobjekten, wobei der Schlüssel die Eingabe-Ganzzahl ist. Sobald die Methode aufgerufen wird, sucht sie das Objekt und ruft die generische Methode für das Objekt auf. Auf diese Weise kann ich die Verwendung des Switch-Case-Konstrukts vermeiden.

Was denken Sie?

10

Es gibt jetzt 50 Fälle im Schalter und jedes Mal, wenn ich einen weiteren Fall hinzufügen muss, schaudere ich.

Ich liebe Polymorphismus. Ich liebe SOLID. Ich liebe reine objektorientierte Programmierung. Ich hasse es, wenn diese einen schlechten Ruf bekommen, weil sie dogmatisch angewendet werden.

Sie haben sich nicht gut dafür ausgesprochen, die Strategie umzugestalten. Das Refactoring hat übrigens einen Namen. Es heißt Ersetzen Sie Bedingt durch Polymorphismus .

Ich habe einige relevante Ratschläge für Sie gefunden von c2.com :

Es ist wirklich nur dann sinnvoll, wenn dieselben oder sehr ähnliche bedingte Tests häufig wiederholt werden. Bei einfachen, selten wiederholten Tests würde das Ersetzen einer einfachen Bedingung durch die Ausführlichkeit mehrerer Klassendefinitionen und wahrscheinlich das Entfernen des Codes, der tatsächlich die bedingt erforderliche Aktivität erfordert, zu einem Lehrbuchbeispiel für die Verschleierung von Code führen. Ziehen Sie Klarheit der dogmatischen Reinheit vor. - DanMuller

Sie haben einen Schalter mit 50 Fällen und Ihre Alternative besteht darin, 50 Objekte zu produzieren. Oh und 50 Zeilen Objektkonstruktionscode. Dies ist kein Fortschritt. Warum nicht? Da dieses Refactoring nichts dazu beiträgt, die Anzahl von 50 zu verringern. Sie verwenden dieses Refactoring, wenn Sie feststellen, dass Sie eine andere switch-Anweisung für dieselbe Eingabe an einer anderen Stelle erstellen müssen. Dann hilft dieses Refactoring, weil es aus 100 wieder 50 macht.

Solange Sie sich auf "den Schalter" beziehen, als wäre er der einzige, den Sie haben, empfehle ich dies nicht. Der einzige Vorteil des Refactorings besteht darin, dass die Wahrscheinlichkeit verringert wird, dass ein Goofball Ihren 50-Fall-Schalter kopiert und einfügt.

Ich empfehle, diese 50 Fälle genau auf Gemeinsamkeiten zu untersuchen, die herausgerechnet werden können. Ich meine 50? "Ja wirklich?" Bist du sicher, dass du so viele Fälle brauchst? Vielleicht versuchen Sie hier zu viel zu tun.

13
candied_orange

Eine Karte mit Strategieobjekten allein, die in einer Funktion Ihres Codes initialisiert wird, in der mehrere Codezeilen aussehen

     myMap.Add(1,new Strategy1());
     myMap.Add(2,new Strategy2());
     myMap.Add(3,new Strategy3());

sie und Ihre Kollegen müssen die Funktionen/Strategien, die in separaten Klassen aufgerufen werden sollen, einheitlicher implementieren (da Ihre Strategieobjekte alle dieselbe Schnittstelle implementieren müssen). Ein solcher Code ist oft etwas umfassender als

     case 1:
          MyClass1.Doit1(someParameters);
          break;
     case 2:
          MyClass2.Doit2(someParameters);
          break;
     case 3:
          MyClass3.Doit3(someParameters);
          break;

Sie werden jedoch immer noch nicht von der Last befreit, diese Codedatei zu bearbeiten, wenn eine neue Nummer hinzugefügt werden muss. Die wirklichen Vorteile dieses Ansatzes sind anders:

  • die Initialisierung der Karte wird nun von dem Versandcode getrennt, der tatsächlich ruft die einer bestimmten Nummer zugeordnete Funktion aufruft, und letztere enthält nicht diese 50 Wiederholungen nicht mehr wird nur so aussehen wie myMap[number].DoIt(someParameters). Dieser Versandcode muss also nicht bei jedem Eintreffen einer neuen Nummer berührt werden und kann nach dem Open-Closed-Prinzip implementiert werden. Wenn Sie Anforderungen erhalten, bei denen Sie den Versandcode selbst erweitern müssen, müssen Sie nicht mehr 50 Stellen ändern, sondern nur noch eine.

  • der Inhalt der Karte wird zur Laufzeit bestimmt (während der Inhalt des Switch-Konstrukts vor der Kompilierungszeit bestimmt wird), sodass Sie die Initialisierungslogik flexibler oder erweiterbarer gestalten können.

Ja, es gibt einige Vorteile, und dies ist sicherlich ein Schritt in Richtung mehr SOLID Code. Wenn sich die Umgestaltung auszahlt, müssen Sie oder Ihr Team dies jedoch selbst entscheiden. Wenn Sie nicht erwarten, dass der Versandcode geändert wird, die Initialisierungslogik geändert wird und die Lesbarkeit von switch kein echtes Problem darstellt, ist Ihr Refactoring jetzt möglicherweise nicht so wichtig.

9
Doc Brown

Ich bin stark für die Strategie in die Antwort von @DocBrown .

Ich werde eine Verbesserung der Antwort vorschlagen.

Die Anrufe

 myMap.Add(1,new Strategy1());
 myMap.Add(2,new Strategy2());
 myMap.Add(3,new Strategy3());

kann verteilt werden. Sie müssen nicht zur selben Datei zurückkehren, um eine weitere Strategie hinzuzufügen, die dem Open-Closed-Prinzip noch besser entspricht.

Angenommen, Sie implementieren Strategy1 in der Datei Strategy1.cpp. Sie können den folgenden Codeblock darin haben.

namespace Strategy1_Impl
{
   struct Initializer
   {
      Initializer()
      {
         getMap().Add(1, new Strategy1());
      }
   };
}
using namespace Strategy1_Impl;

static Initializer initializer;

Sie können denselben Code in jeder StategyN.cpp-Datei wiederholen. Wie Sie sehen können, wird dies eine Menge wiederholter Codes sein. Um die Codeduplizierung zu reduzieren, können Sie eine Vorlage verwenden, die in eine Datei eingefügt werden kann, auf die alle Strategy -Klassen zugreifen können.

namespace StrategyHelper
{
   template <int N, typename StrategyType> struct Initializer
   {
      Initializer()
      {
         getMap().Add(N, new StrategyType());
      }
   };
}

Danach müssen Sie in Strategy1.cpp nur noch Folgendes verwenden:

static StrategyHelper::Initializer<1, Strategy1> initializer;

Die entsprechende Zeile in StrategyN.cpp lautet:

static StrategyHelper::Initializer<N, StrategyN> initializer;

Sie können die Verwendung von Vorlagen auf eine andere Ebene heben, indem Sie eine Klassenvorlage für die konkreten Strategieklassen verwenden.

class Strategy { ... };

template <int N> class ConcreteStrategy;

Und dann anstelle von Strategy1, verwenden ConcreteStrategy<1>.

template <> class ConcreteStrategy<1> : public Strategy { ... };

Ändern Sie die Hilfsklasse, um Strategys zu registrieren:

namespace StrategyHelper
{
   template <int N> struct Initializer
   {
      Initializer()
      {
         getMap().Add(N, new ConcreteStrategy<N>());
      }
   };
}

Ändern Sie den Code in Strateg1.cpp in:

static StrategyHelper::Initializer<1> initializer;

Ändern Sie den Code in StrategN.cpp in:

static StrategyHelper::Initializer<N> initializer;
0
R Sahu