Andernfalls - Wiederholte Codelogik

15

Mein Chef gab mir ein Projekt mit einer bestimmten Logik. Ich muss eine Webseite entwickeln, die den Navigator durch viele Fälle führt, bis er das Produkt erreicht.

Dies ist das Pfadschema der Navigation auf der Site:

Pfadschema

WICHTIG!

Auf der Seite "Produkte" kann der Navigator den gewünschten Filter auswählen.

  • Wenn A, MUSS er / sie durch das B (und dann natürlich C) oder C gehen und die Produkte erreichen.
  • Wenn B, MUSS er / sie das C durchgehen und die Produkte erreichen.
  • Wenn C, erreicht er / sie direkt die Produkte.

Wenn ich von AI aus beginne, folge ich dem längsten Weg und wenn ich meine Produkte erreiche, habe ich 3 aktive Filter.

Bis jetzt habe ich den folgenden Code entwickelt, der gut funktioniert.

if filter_A
  if filter_B
     filter_C()
     .. else ..
  else
     filter_C
    .. else ..
else
   if filter_B
      filter_C()
     .. else ..
   else
     filter_C()
     .. else ..

Ich bin hier, um zu fragen, was ein erfahrener Programmierer in dieser Situation getan hätte. Ich habe das DRY-Prinzip nicht respektiert, es gefällt mir nicht und ich möchte einen alternativen Weg kennen, um diese Art von Logik zu entwickeln.

Ich habe darüber nachgedacht, jeden Codeabschnitt in Funktionen aufzuteilen. Ist dies in diesem Fall eine gute Idee?

Kevin Cittadini
quelle
Das Kontrollflussdiagramm zeigt alle durchgeführten Kontrollen filter_C, aber die bedingten Anweisungen zeigen an, dass der Kontrollfluss umgangen werden kann filter_C. Ist filter_Coptional
CurtisHx
@CurtisHx Filter C ist obligatorisch. Ja, tut mir leid, mein Fehler, den ich beim Kopieren und Einfügen gemacht habe.
Kevin Cittadini
2
Wie kann diese Frage sprachunabhängig sein ? Eine idiomatische Lösung in Java würde sich sehr von einer idiomatischen Lösung in Haskell unterscheiden. Haben Sie sich nicht für eine Sprache für Ihr Projekt entschieden?
200_success

Antworten:

20

Sie haben nicht gesagt, ob die Filter Parameter annehmen. Beispielsweise filter_Akönnte es sich um einen Kategoriefilter handeln, sodass es nicht nur um "Muss ich mich bewerben? filter_A", Sondern auch um "Ich muss mich bewerben filter_Aund alle Datensätze mit dem Kategoriefeld = fooCategory" zurückgeben.

Der einfachste Weg, genau das zu implementieren, was Sie beschrieben haben (aber lesen Sie unbedingt die zweite Hälfte der Antwort weiter unten), ist ähnlich wie bei den anderen Antworten, aber ich hätte überhaupt keine booleschen Prüfungen. Ich würde definieren Schnittstellen: FilterA, FilterB, FilterC. Dann können Sie so etwas haben (ich bin ein Java-Programmierer, daher wird dies eine Java-ähnliche Syntax sein):

class RequestFilters {
    FilterA filterA;
    FilterB filterB;
    FilterC filterC;
}

Dann könnten Sie so etwas haben (unter Verwendung des Enum Singleton-Musters von Effective Java ):

enum NoOpFilterA implements FilterA {
    INSTANCE;

    public List<Item> applyFilter(List<Item> input) {
       return input;
    }
}

Wenn Sie jedoch tatsächlich möchten, dass einige Elemente gefiltert werden, können Sie stattdessen eine Instanz einer FilterAImplementierung bereitstellen , die tatsächlich etwas bewirkt. Ihre Filtermethode wird sehr einfach sein

List<Item> filterItems(List<Item> data, RequestFilters filters) {
    List<Item> returnedList = data;
    returnedList = filters.filterA.filter(data);
    returnedList = filters.filterB.filter(data);
    returnedList = filters.filterC.filter(data);
    return returnedList;
}

Aber ich fange gerade erst an.

Ich vermute, dass der applyFilterAufruf für alle drei Filtertypen tatsächlich ziemlich ähnlich sein wird. Wenn das der Fall ist, würde ich es nicht einmal so machen, wie oben beschrieben. Sie können noch saubereren Code erhalten, indem Sie nur eine Schnittstelle haben und dann folgendermaßen vorgehen:

class ChainedFilter implements Filter {
     List<Filter> filterList;

     void addFilter(Filter filter) {
          filterList.add(filter);
     }

     List<Item> applyFilter(List<Item> input) {
         List<Item> returnedList = input;
         for(Filter f : filterList) {
             returnedList = f.applyFilter(returnedList);
         }
         return returnedList;
     }
}

Wenn Ihr Benutzer dann durch die Seiten navigiert, fügen Sie bei Bedarf einfach eine neue Instanz des gewünschten Filters hinzu. Auf diese Weise können Sie mehrere Instanzen desselben Filters mit unterschiedlichen Argumenten anwenden, falls Sie dieses Verhalten in Zukunft benötigen. Außerdem können Sie in Zukunft weitere Filter hinzufügen, ohne Ihr Design ändern zu müssen .

Darüber hinaus können Sie entweder so etwas wie das NoOpFilteroben Genannte oder nur einen bestimmten Filter überhaupt nicht zur Liste hinzufügen, was auch immer für Ihren Code einfacher ist.

durron597
quelle
Vielen Dank, denn Sie finden die einfachste Möglichkeit, die Logik zu ändern, ohne auch den Code zu ändern. Dies macht Ihre Antwort am besten. Ich werde diesen Code so schnell wie möglich implementieren
Kevin Cittadini
Wenn Sie Ihr Filterals Predicatedann hatten, konnten Sie es direkt in der StreamAPI verwenden. Viele Sprachen haben ähnliche funktionale Konstrukte.
Boris die Spinne
3
@ BoristheSpider Das ist nur, wenn er Java 8 verwendet; Er sagte nicht einmal, welche Sprache er benutzte. Andere Sprachen haben ein solches Konstrukt, aber ich wollte nicht auf die verschiedenen
Arten eingehen,
3
Verstanden - es ist nur erwähnenswert, dass dies eine Möglichkeit ist, herauszufinden, ob das OP eine möglichst saubere Implementierung gewährleisten möchte. Sie haben sicherlich meine +1 für eine bereits ausgezeichnete Antwort.
Boris die Spinne
3

In diesem Fall ist es wichtig, die Filterlogik und den Steuerungsfluss der Filterausführung zu trennen. Die Filterlogik sollte in einzelne Funktionen aufgeteilt werden, die unabhängig voneinander ablaufen können.

ApplyFilterA();
ApplyFilterB();
ApplyFilterC();

In dem Beispielcode geschrieben, gibt es 3 booleans filter_A, filter_Bund filter_C. Aus dem Diagramm filter_Cläuft jedoch immer, so dass auf eine unbedingte umgestellt werden kann.

HINWEIS: Ich gehe davon aus, dass das Kontrollflussdiagramm korrekt ist. Es besteht eine Diskrepanz zwischen dem angegebenen Beispielcode und dem Kontrollflussdiagramm.

Ein separater Code steuert, welche Filter ausgeführt werden

ApplyFilters(bool filter_A, bool filter_B)
{
    listOfProducts tmp;
    if (filter_A)
        ApplyFilterA();
    if (filter_B)
        ApplyFilterB();
    ApplyFilterC();
}

Es gibt eine deutliche Trennung zwischen der Steuerung, welche Filter ausgeführt werden, und der Filterfunktion. Zerbrich diese beiden Teile der Logik.

CurtisHx
quelle
+1 Dies scheint viel einfacher und entkoppelt zu sein als die akzeptierte Antwort.
winkbrace
2

Ich gehe davon aus, dass Sie den einfachsten, klarsten Algorithmus wollen.
In diesem Fall würde ich, da ich weiß, dass Filter c immer angewendet wird, es aus der if-Logik heraus leben und es trotzdem am Ende anwenden. Wie es in Ihrem Flussdiagramm aussieht, ist jeder Filter vor dem c optional, da jeder von ihnen entweder angewendet werden kann oder nicht. In diesem Fall würde ich ifs getrennt von jedem Filter leben, ohne es zu verschachteln und zu verketten:

if filter_a
  do_filter_a()

if filter_b
  do_filter_b()

do_filter_c()

Wenn Sie ein Flussdiagramm mit einer variablen Anzahl von Filtern haben, würde ich stattdessen alle Filter in einem Array in der Reihenfolge speichern, in der sie angezeigt werden sollen. Verarbeiten Sie dann optionale Filter in der Schleife und wenden Sie die obligatorische am Ende außerhalb der Schleife an:

optional_filters_array = (a, b, c, d, e, f, g, h, etc)

for current_filter in optional_filters_array
  do_filter(current_filter)

do_required_filter()

oder:

optional_filters_array = (a, b, c, d, e, f, g, h, etc)
required_filter = last_filter


for current_filter in optional_filters_array
  do_filter(current_filter)

do_filter(required_filter)

Natürlich müssten Sie das Unterprogramm für die Filterverarbeitung definieren.

igoryonya
quelle
1

Ich gehe davon aus, dass filterA, filterB und filterC tatsächlich die Liste der Produkte ändern. Andernfalls können filterA und filterB ignoriert werden, wenn es sich nur um If-Checks handelt, da alle Pfade letztendlich zu filterC führen. Ihre Beschreibung der Anforderung scheint zu implizieren, dass jeder Filter die Produktliste verkleinert.

Angenommen, die Filter reduzieren tatsächlich die Liste der Produkte, dann ist hier ein bisschen Pseudocode ...

class filter
    func check(item) returns boolean
endclass

func applyFilter(filter, productList) returns list
    newList is list
    foreach item in productList
        if filter.check(item) then
            add item to newList
        endif
    endfor 
    return newList
endfunc



filterA, filterB, filterC = subclasses of filter for each condition, chosen by the user
products = list of items to be filtered

if filterA then
    products = applyFilter(filterA, products)
endif

if filterB then
    products = applyFilter(filterB, products)
endif

if filterC then
    products = applyFilter(filterC, products)
endif

# use products...

In Ihren Anforderungen wird filterC nicht automatisch angewendet, im Diagramm jedoch. Wenn die Anforderung besteht, dass mindestens filterC angewendet werden soll, würden Sie applyFilter (filterC, products) aufrufen, ohne zu überprüfen, ob filterC ausgewählt ist.

filterC = instance of filter, always chosen

...

# if filterC then
products = applyFilter(filterC, products)
# endif
Kent A.
quelle
0

Ich frage mich, ob die Modellierung Ihrer Filter als Objekte in einem Diagramm sinnvoll wäre. Zumindest denke ich daran, wenn ich das Diagramm sehe.

Wenn Sie die Abhängigkeit der Filter wie ein Objektdiagramm modellieren, ist der Code, der die möglichen Flusspfade verarbeitet, ziemlich einfach und ohne haarige Logik. Das Diagramm (Geschäftslogik) kann sich auch ändern, während der Code, der das Diagramm interpretiert, gleich bleibt.

enum
quelle