If Else - Lógica de Código Repetida

15

Meu chefe me deu um projeto com uma lógica específica. Eu tenho que desenvolver uma página da web que deve liderar o navegador em muitos casos até que ele chegue ao produto.

Este é o esquema de caminho da navegação no site:

Esquema de caminho

IMPORTANTE!

Na página Produtos, o navegador pode escolher qual filtro ele deseja.

  • Se A, ele / ela DEVE passar pelo B (e depois C, é claro) ou C e alcançar os produtos.
  • Se B, ele / ela DEVE passar pelo C e alcançar os produtos.
  • Se C, ele / ela alcança diretamente os produtos.

Obviamente, se eu começar da IA, estou seguindo o caminho mais longo e, quando chego aos meus produtos, tenho 3 filtros ativos.

Até agora desenvolvi o seguinte código, que funciona bem.

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

Estou aqui para perguntar o que um programador mais experiente teria feito nessa situação. Não respeitei o princípio DRY, não gosto e gostaria de conhecer uma maneira alternativa de desenvolver esse tipo de lógica.

Pensei em dividir cada seção do código em funções, mas é uma boa idéia neste caso?

Kevin Cittadini
fonte
O diagrama de fluxo de controle mostra todo o controle passando filter_C, mas as instruções condicionais indicam que o fluxo de controle pode ser contornado filter_C. É filter_Copcional?
CurtisHx
O @CurtisHx Filter C é obrigatório. Sim desculpe meu erro eu fiz copiar e colar.
22815 Kevin Kevin Cittadini
2
Como essa pergunta pode ser independente de idioma ? Uma solução idiomática em Java seria muito diferente de uma solução idiomática em Haskell. Você não decidiu um idioma para o seu projeto?
200

Respostas:

20

Você não disse se os filtros têm algum parâmetro. Por exemplo, filter_Apode ser um filtro de categoria, para que não seja apenas uma questão de "preciso aplicar filter_A", mas sim "preciso aplicar filter_Ae retornar todos os registros com o campo de categoria = fooCategory".

A maneira mais simples de implementar exatamente o que você descreveu (mas não deixe de ler a segunda metade da resposta abaixo) é semelhante às outras respostas, mas eu não teria nenhuma verificação booleana. Gostaria de definir interfaces: FilterA, FilterB, FilterC. Então você pode ter algo como (eu sou um programador Java, então essa será a sintaxe em estilo Java):

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

Então você pode ter algo parecido com isto (usando o padrão enum singleton do Effective Java ):

enum NoOpFilterA implements FilterA {
    INSTANCE;

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

Mas se você realmente deseja filtrar alguns itens, pode fornecer uma instância de uma FilterAimplementação que realmente faz alguma coisa. O seu método de filtragem será muito simples

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

Mas estou apenas começando.

Suspeito que a applyFilterchamada seja realmente semelhante para todos os três tipos de filtros. Se for esse o caso, eu nem faria da maneira descrita acima. Você pode obter um código ainda mais limpo, tendo apenas uma interface e fazendo o seguinte:

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

Em seguida, à medida que o usuário navega pelas páginas, basta adicionar uma nova instância de qualquer filtro necessário, quando apropriado. Isso permitirá que você possa aplicar várias instâncias do mesmo filtro com argumentos diferentes, caso precise desse comportamento no futuro, além de adicionar filtros adicionais no futuro sem precisar alterar seu design .

Além disso, você pode adicionar algo como o NoOpFilterdescrito acima ou simplesmente não pode adicionar um filtro específico à lista, o que for mais fácil para o seu código.

durron597
fonte
Obrigado, porque você encontra a maneira mais simples possível de alterar a lógica sem alterar o código também. Isso torna sua resposta a melhor. Implementarei esse design de código o mais rápido possível
Kevin Cittadini
Se você teve o seu Filtercomo, Predicateentão você pode usá-lo diretamente na StreamAPI. Muitos idiomas têm construções funcionais semelhantes.
Boris, a Aranha
3
@BoristheSpider Isso é apenas se ele estiver usando Java 8; ele nem mesmo disse que idioma estava usando. Outras línguas têm uma tal construção, mas eu não quero entrar em todos os diferentes sabores de como fazer isso
durron597
3
Entendido - vale ressaltar que esse é um caminho a ser explorado se o OP deseja fornecer a implementação mais limpa possível. Você certamente tem o meu +1 para uma resposta já excelente.
Boris, a Aranha
3

Nesse caso, é importante separar a lógica da filtragem e o fluxo de controle de como os filtros são executados. A lógica do filtro deve ser separada em funções individuais, que podem funcionar independentemente uma da outra.

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

No código de exemplo postado, há 3 booleans filter_A, filter_Be filter_C. No entanto, no diagrama, filter_Csempre é executado, para que possa ser alterado para um incondicional.

NOTA: Estou assumindo que o diagrama de fluxo de controle está correto. Há uma discrepância entre o código de amostra publicado e o diagrama de fluxo de controle.

Um trecho de código separado controla quais filtros são executados

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

Há uma separação distinta entre o controle de quais filtros são executados e o que os filtros fazem. Quebre esses dois pedaços de lógica.

CurtisHx
fonte
+1 Isso parece muito mais simples e dissociado do que a resposta aceita.
winkbrace
2

Suponho que você deseja o algoritmo mais simples e claro.
Nesse caso, sabendo que o filtro c é sempre aplicado, eu o tiraria da lógica if e o aplicaria no final independentemente. Como parece em seu fluxograma, cada filtro antes de c é opcional, porque cada um deles pode ser aplicado ou não. Nesse caso, eu viveria ifs separados de cada filtro, sem aninhamento e encadeamento:

if filter_a
  do_filter_a()

if filter_b
  do_filter_b()

do_filter_c()

se você tiver um fluxograma com um número variável de filtros, antes do obrigatório, eu salvaria todos os filtros em uma matriz, na ordem em que eles deveriam aparecer. Em seguida, processe filtros opcionais no loop e aplique o obrigatório no final, fora do loop:

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)

Por exemplo, você teria que definir a sub-rotina de processamento do filtro.

igoryonya
fonte
1

Vou assumir que filterA, filterB e filterC realmente modificam a lista de produtos. Caso contrário, se forem apenas verificações de if, o filterA e o filterB poderão ser ignorados, pois todos os caminhos levam ao filterC. Sua descrição do requisito parece implicar que cada filtro reduzirá a lista de produtos.

Supondo que os filtros realmente reduzam a lista de produtos, aqui está um pouco de pseudo-código ...

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

Nos seus requisitos, o filterC não é aplicado automaticamente, mas no diagrama, é. Se o requisito é que pelo menos filterC deva ser aplicado independentemente do que seja, você chamaria applyFilter (filterC, products) sem verificar se o filterC é escolhido.

filterC = instance of filter, always chosen

...

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

Gostaria de saber se a modelagem de seus filtros para ser algum tipo de objeto em um gráfico faria sentido. Pelo menos é o que penso quando vejo o diagrama.

Se você modelar a dependência dos filtros como um gráfico de objeto, o código que lida com os possíveis caminhos de fluxo é bastante simples, sem lógica lógica. Além disso, o gráfico (lógica de negócios) pode mudar, enquanto o código que interpreta o gráfico permanece o mesmo.

enum
fonte