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 web que tem que liderar o navegador em muitos casos até que ele chegue ao produto.

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

IMPORTANTE!

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

  • Se A, ele / ela DEVE passar pelo B (e, em seguida, 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 alcança diretamente os produtos.

É claro que, se eu começar de A, estou seguindo o caminho mais longo e, quando alcançar meus produtos, tenho três 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. Eu não respeitei o princípio DRY, não gostei e gostaria de saber uma forma alternativa de desenvolver esse tipo de lógica.

Eu pensei em dividir cada seção do código em funções, mas é uma boa ideia neste caso?

    
por Kevin Cittadini 06.03.2015 / 13:18
fonte

5 respostas

20

Você não disse se os filtros aceitam algum parâmetro. Por exemplo, filter_A pode ser um filtro de categoria, para que não seja apenas uma questão de "eu preciso aplicar filter_A ", poderia ser "Eu preciso aplicar filter_A e retornar todos os registros com a categoria campo = fooCategory ".

A maneira mais simples de implementar exatamente o que você descreveu (mas tenha certeza de ler a segunda metade da resposta abaixo) é similar às outras respostas, mas eu não teria nenhum booleano verifica em tudo. Eu definiria interfaces: FilterA, FilterB, FilterC . Então você pode ter algo como (eu sou um programador Java, então esta será a sintaxe do Java):

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

Você pode ter algo assim (usando o enum padrão singleton de Java efetivo ):

enum NoOpFilterA implements FilterA {
    INSTANCE;

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

Mas, se você quiser que alguns itens sejam filtrados, forneça uma instância de uma implementação FilterA que realmente faça alguma coisa. 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.

Eu suspeito que a chamada applyFilter seja realmente similar 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 isso:

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 que você precise, quando apropriado. Isso permitirá que você aplique várias instâncias do mesmo filtro com argumentos diferentes caso precise desse comportamento no futuro e também adicione filtros adicionais no futuro sem precisar alterar seu design .

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

    
por 06.03.2015 / 15:17
fonte
3

Nesse caso, é importante separar a lógica de 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 ser independentes umas das outras.

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

No exemplo de código postado, há 3 booleans filter_A , filter_B e filter_C . No entanto, no diagrama, filter_C sempre é executado, de modo que pode ser alterado para 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 pedaço 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();
}

Existe uma separação distinta entre controlar quais filtros são executados e o que os filtros fazem. Quebre essas duas partes da lógica.

    
por 06.03.2015 / 14:12
fonte
2

Eu assumo que você quer o algoritmo mais simples e claro.
Nesse caso, sabendo que o filtro c sempre é aplicado, eu o levaria para fora da lógica if e aplicaria no final, independentemente disso. Como parece no seu fluxograma, cada filtro antes do c é opcional, porque cada um deles pode ser aplicado ou não. Neste 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, em uma ordem em que eles deveriam aparecer. Em seguida, processe os 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)

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

    
por 06.03.2015 / 16:41
fonte
1

Suponho que filterA, filterB e filterC realmente modificam a lista de produtos. Caso contrário, se forem apenas if-checks, então filterA e filterB podem ser ignorados, pois todos os caminhos levam, em última instância, ao filterC. Sua descrição do requisito parece implicar que cada filtro reduzirá a lista de produtos.

Então, 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...

Em seus requisitos, o filterC não é aplicado automaticamente, mas no diagrama, é. Se o requisito é que pelo menos o filterC deve ser aplicado não importa o que, então 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
    
por 06.03.2015 / 14:05
fonte
0

Gostaria de saber se a modelagem de seus filtros como 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, então o código que lida com os possíveis caminhos de fluxo é bastante direto sem qualquer lógica complicada. 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.

    
por 06.03.2015 / 14:30
fonte