Refatorando as instruções do switch e existe algum uso real para as instruções do switch?


28

Eu estava lendo este artigo e fiquei pensando: será que vamos nos livrar de todas as instruções de troca, substituindo-as por um Dicionário ou uma Fábrica, para que não haja nenhuma declaração de troca em meus projetos?

Algo não deu certo.

A questão é: as instruções switch têm algum uso real ou as substituímos por um dicionário ou um método factory (ao usar um método factory, é claro, haverá um uso mínimo das instruções switch para criar os objetos usando a fábrica ... mas é isso).


10
Supondo que você implemente uma fábrica, como você decidirá que tipo de objeto criar?
CodeART


@ CodeWorks: Obviamente, terei condições em algum lugar, decidindo qual implementação concreta usar.
Kanini

@ CodeWorks: Com um construtor virtual, obviamente. (E se você não pode implementar um padrão de fábrica dessa maneira, você precisa de uma linguagem melhor.)
Mason Wheeler

Respostas:


44

Ambas as switchafirmações e o polimorfismo têm seu uso. Observe que também existe uma terceira opção (em idiomas que suportam ponteiros de função / lambdas e funções de ordem superior): mapeando os identificadores em questão para funções de manipulador. Está disponível em, por exemplo, C, que não é uma linguagem OO, e C #, que é *, mas não (ainda) em Java, que também é OO *.

Em algumas linguagens processuais (sem polimorfismo nem funções de ordem superior) switch/ if-elsedeclarações eram a única maneira de resolver uma classe de problemas. Muitos desenvolvedores, acostumados a esse modo de pensar, continuaram a usar switchmesmo nas linguagens OO, onde o polimorfismo costuma ser uma solução melhor. É por isso que é frequentemente recomendado evitar / refatorar switchdeclarações em favor do polimorfismo.

De qualquer forma, a melhor solução depende sempre do caso. A questão é: qual opção oferece um código mais limpo, conciso e mais sustentável a longo prazo?

As instruções de troca geralmente podem se tornar pesadas, tendo dezenas de casos, dificultando a manutenção. Como você precisa mantê-los em uma única função, essa função pode crescer muito. Se for esse o caso, considere refatorar para uma solução baseada em mapa e / ou polimórfica.

Se o mesmo switchcomeçar a aparecer em vários lugares, o polimorfismo é provavelmente a melhor opção para unificar todos esses casos e simplificar o código. Especialmente se se espera que mais casos sejam adicionados no futuro; quanto mais lugares você precisar atualizar a cada vez, mais possibilidades de erros. No entanto, geralmente os manipuladores de casos individuais são tão simples, ou existem muitos deles, ou são tão inter-relacionados, que refatorá-los para uma hierarquia de classes polimórfica completa é um exagero ou resulta em muitos códigos duplicados e / ou emaranhados, difícil manter a hierarquia de classes. Nesse caso, pode ser mais simples usar funções / lambdas (se o seu idioma permitir).

No entanto, se você tiver um switchem um único local, com apenas alguns casos fazendo algo simples, pode ser a melhor solução para deixá-lo como está.

* Eu uso o termo "OO" aqui vagamente; Não estou interessado em debates conceituais sobre o que é OO "real" ou "puro".


4
+1. Além disso, essas recomendações tendem a ser redigidas, " preferem o polimorfismo à troca" e essa é uma boa escolha de palavras, muito de acordo com esta resposta. Ele reconhece que há circunstâncias em que um codificador responsável pode fazer a outra escolha.
Carl Manaster

11
E há momentos em que você deseja que a troca se aproxime, mesmo que haja um zilhão deles no seu código. Eu tenho um pedaço de código em mente que gera um labirinto em 3D. O uso da memória aumentaria se as células de um byte na matriz fossem substituídas por classes.
Loren Pechtel

14

É aqui que eu volto a ser um dinossauro ...

As declarações de switch não são ruins em si mesmas, é o uso que é feito delas que está em questão.

A mais óbvia é a declaração "switch" repetida várias vezes através de seu código que é ruim (esteve lá, fez isso, faria todos os esforços para não repetir) - e é este último caso que você pode lidar com o uso de polimorfismo. Geralmente, também há algo bastante horrível nos casos aninhados (eu costumava ter um monstro absoluto - não tenho muita certeza de como lidar com isso agora, a não ser "melhor").

Dicionário como opção, acho mais desafiador - fundamentalmente sim, se sua opção cobre 100% dos casos, mas onde você deseja ter casos de ação padrão ou sem ação, ela começa a ficar um pouco mais interessante.

Acho que é uma questão de evitar repetições e garantir que estamos compondo nossos gráficos de objetos nos lugares certos.

Mas há também o argumento de compreensão (manutenibilidade) e isso corta nos dois sentidos - depois que você entende como tudo funciona (o padrão e o aplicativo em que é implementado) é fácil ... mas se você chegar a uma única linha de código em que Para adicionar algo novo, é preciso pular por todo o lado para descobrir o que você precisa adicionar / alterar.

Por tudo o que nossos ambientes de desenvolvimento são massivamente capazes, ainda sinto que é desejável entender o código (como se fosse) impresso no papel - você pode seguir o código com o dedo? Aceito que, na verdade, não, com muitas práticas recomendadas hoje em dia, você não pode e por boas razões, mas isso significa que é mais difícil começar a se familiarizar com o código (ou talvez eu esteja velho ...)


4
Eu tive um professor que disse que o código ideal deveria ser escrito "para que alguém que não soubesse nada sobre programação (como talvez sua mãe) pudesse entendê-lo". Infelizmente esse é o ideal, mas talvez devêssemos incluir nossas mães nas revisões de código de qualquer maneira!
Michael K

+1 para "mas se você encontrar uma única linha de código em que precisará adicionar algo novo, precisará saltar por todo o lado para descobrir o que precisa adicionar / alterar"
rapid_now

8

Instruções de comutação versus polimorfismo de subtipo é um problema antigo e é frequentemente referido na comunidade FP em discussões sobre o Problema da Expressão .

Basicamente, temos tipos (classes) e funções (métodos). Como codificamos as coisas para facilitar a adição de novos tipos ou métodos?

Se você programar no estilo OO, é difícil adicionar um novo método (pois isso significaria refatorar todas as classes existentes), mas é muito fácil adicionar novas classes que usem os mesmos métodos de antes.

Por outro lado, se você usar uma instrução switch (ou seu equivalente OO, o Observer Pattern), é muito fácil adicionar novas funções, mas é difícil adicionar novos casos / classes.

Não é fácil ter uma boa extensibilidade em ambas as direções; portanto, ao escrever seu código, determine se deve usar polimorfismo ou alternar instruções, dependendo de qual direção você tem mais chances de estender.


4
nos livramos de todas as instruções de troca, substituindo-as por um Dicionário ou uma Fábrica, para que não haja nenhuma declaração de troca em meus projetos.

Não. Esses absolutos raramente são uma boa idéia.

Em muitos lugares, um despacho de dicionário / pesquisa / fábrica / polimórfico fornecerá um design melhor que uma instrução switch, mas você ainda precisará preencher o dicionário. Em certos casos, isso obscurecerá o que realmente está acontecendo e simplesmente manter a instrução switch alinhada é mais legível e sustentável.


E realmente, se você desenrolasse a fábrica, o dicionário e a pesquisa, seria basicamente uma declaração de troca: se array [hash (pesquisa)] chame array [hash (pesquisa)]. Embora fosse extensível em tempo de execução, as opções compiladas não são.
Zan Lynx

@ZanLynx, o oposto completo é verdadeiro, pelo menos com C #. Se você observar a IL produzida para uma instrução de troca não trivial, verá que o compilador a transforma em um dicionário. Por ser mais rápido.
22615 David Arno

@ DavidArno: "o completo oposto"? Pelo jeito que li, dissemos exatamente a mesma coisa. Como pode ser o oposto?
Zan Lynx 10/10

2

Vendo como isso é independente de idioma, o código de resultado funciona melhor com as switchinstruções:

switch(something) {
   case 1:
      foo();
   case 2:
      bar();
      baz();
      break;

   case 3:
      bang();
   default:
      bizzap();
      break;
}

Equivalente a if, com um caso padrão muito estranho. E lembre-se de que quanto mais falhas houver, mais longa será a lista de condições:

if (1 == something) {
   foo();
}
if (1 == something || 2 == something) {
   bar();
   baz();
}
if (3 == something) {
   bang();
}
if (1 != something && 2 != something) {
   bizzap();
}

(No entanto, considerando o que algumas das outras respostas dizem, sinto que perdi o objetivo da pergunta ...)


Eu consideraria o uso de declarações de resultados em um switchcomo estando no mesmo nível que um goto. Se o algoritmo a ser executado exigir fluxos de controle que se encaixam nas construções de programação estruturada, deve-se usar essas construções, mas se o algoritmo não se encaixar nessas construções, o uso gotopode ser melhor do que tentar adicionar sinalizadores ou outra lógica de controle para caber em outras estruturas de controle .
Supercat 14/07

1

As instruções de switch como diferenciação de caso têm um uso real. As linguagens de programação funcional usam algo chamado correspondência de padrões, que permite definir funções de maneira diferente, dependendo da entrada. No entanto, nas linguagens orientadas a objetos, o mesmo objetivo pode ser alcançado de maneira mais elegante usando o polimorfismo. Você simplesmente chama o método e, dependendo do tipo real do objeto, a implementação correspondente do método será executada. Esta é a razão por trás da ideia, que as instruções de switch são um cheiro de código. No entanto, mesmo em idiomas OO, você pode encontrá-los úteis para implementar o padrão abstrato de fábrica.

tl; dr - eles são úteis, mas muitas vezes você pode fazer melhor.


2
Sinto algum viés lá - por que o polimorfismo é mais elegante do que a correspondência de padrões? E o que faz você pensar que o polimorfismo não existe no PF?
Tdammers

@ Tdammers Antes de mais nada, eu não quis dizer que o polimorfismo não existe no FP. Haskell suporta polimorfismo ad hoc e paramétrico. A correspondência de padrões faz sentido para um tipo de dados algébrico. Mas para módulos externos, isso requer expor construtores. Claramente, isso quebra o encapsulamento de um tipo de dados abstrato (realizado com tipos de dados algébricos). É por isso que sinto que o polimorfismo é mais elegante (e possível no FP).
scarfridge

Você está esquecendo o polimorfismo através de classes e instâncias.
tdammers

Você já ouviu falar do problema da expressão ? O OO e o FP são melhores em diferentes situações, se você parar para pensar sobre isso.
Hugomg #

@tdammers Que tipo de polimorfismo você acha que eu estava falando quando mencionei polimorfismo ad hoc? O polimorfismo ad hoc é realizado em Haskell através de classes de tipo. Como você pode dizer que eu esqueci? Simplesmente não é verdade.
Scarfridge 5/05
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.