Sinon - Logique de code répétée

15

Mon patron m'a donné un projet avec une logique particulière. Je dois développer une page Web qui doit guider le navigateur à travers de nombreux cas jusqu'à ce qu'il arrive au produit.

Voici le schéma des chemins de navigation dans le site:

Schéma de chemin

IMPORTANT!

Dans la page Produits, le navigateur peut choisir le filtre qu'il souhaite.

  • Si A, il / elle DOIT passer par le B (puis C bien sûr) ou C et atteindre les produits.
  • Si B, il / elle DOIT passer par le C et atteindre les produits.
  • Si C, il accède directement aux produits.

Bien sûr, si je pars de l'IA, je suis sur le chemin le plus long et quand j'atteins mes produits, j'ai 3 filtres actifs.

Jusqu'à présent, j'ai développé le code suivant qui fonctionne bien.

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

Je suis ici pour demander ce qu'aurait fait un programmeur plus expert dans cette situation. Je n'ai pas respecté le principe DRY, je ne l'aime pas et j'aimerais connaître une alternative pour développer ce type de logique.

J'ai pensé à diviser chaque section de code en fonctions, mais est-ce une bonne idée dans ce cas?

Kevin Cittadini
la source
doublon possible de la répétition du code à éviter dans les énoncés de condition
gnat
Le diagramme de flux de contrôle montre tous les contrôles passant filter_C, mais les instructions conditionnelles indiquent que le flux de contrôle peut circuler filter_C. Est filter_Cfacultatif?
CurtisHx
@CurtisHx Filter C est obligatoire. Oui désolé mon erreur j'ai fait du copier-coller.
Kevin Cittadini
2
Comment cette question peut -elle être indépendante de la langue ? Une solution idiomatique en Java serait très différente d'une solution idiomatique en Haskell. Vous n'avez pas encore choisi de langue pour votre projet?
200_success

Réponses:

20

Vous n'avez pas dit si les filtres prenaient des paramètres. Par exemple, il filter_Apeut s'agir d'un filtre de catégorie, de sorte qu'il ne s'agit pas seulement de "dois-je postuler filter_A", il peut s'agir de "je dois postuler filter_Aet renvoyer tous les enregistrements avec le champ de catégorie = fooCategory".

La façon la plus simple de mettre en œuvre exactement ce que vous avez décrit (mais assurez-vous de lire la deuxième moitié de la réponse ci-dessous) est similaire aux autres réponses, mais je n'aurais aucune vérification booléenne du tout. Je définirais interfaces: FilterA, FilterB, FilterC. Ensuite, vous pouvez avoir quelque chose comme (je suis un programmeur Java, donc ce sera la syntaxe Java-esque):

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

Ensuite, vous pouvez avoir quelque chose comme ça (en utilisant le modèle enum singleton de Effective Java ):

enum NoOpFilterA implements FilterA {
    INSTANCE;

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

Mais si vous souhaitez réellement filtrer certains éléments, vous pouvez plutôt fournir une instance d'une FilterAimplémentation qui fait réellement quelque chose. Votre méthode de filtration sera très simple

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

Mais je ne fais que commencer.

Je soupçonne que l' applyFilterappel sera en fait assez similaire pour les trois types de filtres. Si c'est le cas, je ne le ferais même pas comme décrit ci-dessus. Vous pouvez obtenir un code encore plus propre en n'ayant qu'une seule interface, puis procédez comme suit:

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

Ensuite, lorsque votre utilisateur navigue dans les pages, vous ajoutez simplement une nouvelle instance du filtre dont vous avez besoin, le cas échéant. Cela vous permettra d'appliquer plusieurs instances du même filtre avec des arguments différents si vous avez besoin de ce comportement à l'avenir, et également d'ajouter des filtres supplémentaires à l'avenir sans avoir à modifier votre conception .

De plus, vous pouvez ajouter quelque chose comme ce qui NoOpFilterprécède ou vous ne pouvez tout simplement pas ajouter un filtre particulier à la liste, quoi que ce soit plus facile pour votre code.

durron597
la source
Merci, car vous trouvez le moyen le plus simple de changer la logique sans changer le code également. Cela rend votre réponse la meilleure. Je vais implémenter cette conception de code dès que possible
Kevin Cittadini
Si vous aviez Filtercomme Predicatevous pourriez l' utiliser directement dans l' StreamAPI. De nombreux langages ont des constructions fonctionnelles similaires.
Boris the Spider
3
@BoristheSpider C'est seulement s'il utilise Java 8; il n'a même pas dit quelle langue il utilisait. D'autres langues ont une telle construction mais je ne voulais pas entrer dans toutes les différentes saveurs de la façon de le faire
durron597
3
Compris - il convient de mentionner que c'est une avenue à explorer si le PO souhaite fournir la mise en œuvre la plus propre possible. Vous avez certainement mon +1 pour une réponse déjà excellente.
Boris the Spider
3

Dans ce cas, il est important de séparer la logique du filtrage et le flux de contrôle du fonctionnement des filtres. La logique de filtrage doit être séparée en fonctions individuelles, qui peuvent fonctionner indépendamment les unes des autres.

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

Dans l'exemple de code affiché, il y a 3 booléens filter_A, filter_Bet filter_C. Cependant, à partir du diagramme, filter_Cs'exécute toujours, ce qui peut être changé en inconditionnel.

REMARQUE: je suppose que le diagramme de flux de contrôle est correct. Il existe une différence entre l'exemple de code publié et le diagramme de flux de contrôle.

Un morceau de code séparé contrôle les filtres à exécuter

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

Il existe une séparation distincte entre le contrôle des filtres exécutés et leur fonction. Brisez ces deux morceaux de logique.

CurtisHx
la source
+1 Cela semble beaucoup plus simple et découplé que la réponse acceptée.
winkbrace
2

Je suppose que vous voulez l'algorithme le plus simple et le plus clair.
Dans ce cas, sachant que le filtre c est toujours appliqué, je le vivrais hors de la logique if et l'appliquerais à la fin malgré tout. Comme il apparaît dans votre organigramme, chaque filtre avant le c est facultatif, car chacun d'eux peut être appliqué ou non. Dans ce cas, je vivrais des ifs séparés de chaque filtre, sans imbrication ni chaînage:

if filter_a
  do_filter_a()

if filter_b
  do_filter_b()

do_filter_c()

si vous avez un organigramme avec un nombre variable de filtres, avant celui obligatoire, je voudrais plutôt enregistrer tous les filtres dans un tableau, dans un ordre où ils devraient apparaître. Ensuite, traitez les filtres optionnels dans la boucle et appliquez celui obligatoire à la fin, en dehors de la boucle:

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()

ou:

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)

bien sûr, vous devrez définir le sous-programme de traitement du filtre.

igoryonya
la source
1

Je vais supposer que filterA, filterB et filterC modifient réellement la liste des produits. Sinon, si ce ne sont que des vérifications if, alors filterA et filterB peuvent être ignorés car tous les chemins mènent finalement à filterC. Votre description de l'exigence semble impliquer que chaque filtre réduira la liste des produits.

Donc, en supposant que les filtres réduisent réellement la liste des produits, voici un peu de pseudo-code ...

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

Dans vos besoins, filterC n'est pas appliqué automatiquement, mais dans le diagramme, il l'est. Si l'exigence est qu'au moins filterC doit être appliqué quoi qu'il arrive, alors vous appellerez applyFilter (filterC, produits) sans vérifier si filterC est choisi.

filterC = instance of filter, always chosen

...

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

Je me demande si la modélisation de vos filtres en une sorte d’objets dans un graphique aurait du sens. C'est du moins ce à quoi je pense en voyant le diagramme.

Si vous modélisez la dépendance des filtres comme un graphique d'objet, le code qui gère les chemins de flux possibles est à peu près simple sans aucune logique velue. En outre, le graphique (logique métier) peut changer, tandis que le code qui interprète le graphique reste le même.

énumérer
la source