Quais são as maneiras de eliminar o uso da opção no código?
Quais são as maneiras de eliminar o uso da opção no código?
Respostas:
As instruções de comutação não são um antipadrão por si só, mas se você estiver codificando orientação a objeto, deve considerar se o uso de uma opção é melhor resolvido com polimorfismo, em vez de usar uma instrução de comutação.
Com o polimorfismo, isso:
foreach (var animal in zoo) {
switch (typeof(animal)) {
case "dog":
echo animal.bark();
break;
case "cat":
echo animal.meow();
break;
}
}
torna-se o seguinte:
foreach (var animal in zoo) {
echo animal.speak();
}
typeof
, e esta resposta não sugere maneiras ou motivos para contornar as instruções switch em outras situações.
Veja o cheiro das instruções do switch :
Normalmente, instruções de opção semelhantes estão espalhadas por um programa. Se você adicionar ou remover uma cláusula em um switch, também precisará localizar e reparar os outros.
A refatoração e a refatoração de padrões têm abordagens para resolver isso.
Se o seu (pseudo) código se parecer com:
class RequestHandler {
public void handleRequest(int action) {
switch(action) {
case LOGIN:
doLogin();
break;
case LOGOUT:
doLogout();
break;
case QUERY:
doQuery();
break;
}
}
}
Este código viola o Princípio Aberto Fechado e é frágil para cada novo tipo de código de ação que aparece. Para remediar isso, você pode introduzir um objeto 'Comando':
interface Command {
public void execute();
}
class LoginCommand implements Command {
public void execute() {
// do what doLogin() used to do
}
}
class RequestHandler {
private Map<Integer, Command> commandMap; // injected in, or obtained from a factory
public void handleRequest(int action) {
Command command = commandMap.get(action);
command.execute();
}
}
Se o seu (pseudo) código se parecer com:
class House {
private int state;
public void enter() {
switch (state) {
case INSIDE:
throw new Exception("Cannot enter. Already inside");
case OUTSIDE:
state = INSIDE;
...
break;
}
}
public void exit() {
switch (state) {
case INSIDE:
state = OUTSIDE;
...
break;
case OUTSIDE:
throw new Exception("Cannot leave. Already outside");
}
}
Então você pode introduzir um objeto 'State'.
// Throw exceptions unless the behavior is overriden by subclasses
abstract class HouseState {
public HouseState enter() {
throw new Exception("Cannot enter");
}
public HouseState leave() {
throw new Exception("Cannot leave");
}
}
class Inside extends HouseState {
public HouseState leave() {
return new Outside();
}
}
class Outside extends HouseState {
public HouseState enter() {
return new Inside();
}
}
class House {
private HouseState state;
public void enter() {
this.state = this.state.enter();
}
public void leave() {
this.state = this.state.leave();
}
}
Espero que isto ajude.
Map<Integer, Command>
não precisaria de uma opção?
Um switch é um padrão, implementado com uma instrução switch, caso contrário, cadeia, tabela de pesquisa, polimorfismo oop, correspondência de padrões ou qualquer outra coisa.
Deseja eliminar o uso da " instrução switch " ou do " padrão switch "? O primeiro pode ser eliminado, o segundo, apenas se outro padrão / algoritmo puder ser usado, e na maioria das vezes isso não é possível ou não é uma abordagem melhor para fazê-lo.
Se você deseja eliminar a instrução switch do código, a primeira pergunta a fazer é onde faz sentido eliminar a instrução switch e usar alguma outra técnica. Infelizmente, a resposta a esta pergunta é específica do domínio.
E lembre-se de que os compiladores podem fazer várias otimizações para alternar instruções. Portanto, por exemplo, se você deseja processar as mensagens com eficiência, uma instrução switch é praticamente o caminho a percorrer. Por outro lado, a execução de regras de negócios com base em uma instrução switch provavelmente não é o melhor caminho a ser percorrido, e o aplicativo deve ser pesquisado novamente.
Aqui estão algumas alternativas para mudar de instrução:
Mudar por si só não é tão ruim, mas se você tiver muitos "switch" ou "if / else" nos objetos em seus métodos, pode ser um sinal de que seu design é um pouco "processual" e que seus objetos são apenas valor baldes. Mova a lógica para seus objetos, invoque um método em seus objetos e deixe-os decidir como responder.
Eu acho que a melhor maneira é usar um bom mapa. Usando um dicionário, você pode mapear quase qualquer entrada para algum outro valor / objeto / função.
seu código ficaria assim (psuedo) assim:
void InitMap(){
Map[key1] = Object/Action;
Map[key2] = Object/Action;
}
Object/Action DoStuff(Object key){
return Map[key];
}
Todo mundo adora ENORME if else
blocos. Tão fácil de ler! Estou curioso para saber por que você deseja remover as instruções do switch. Se você precisar de uma instrução switch, provavelmente precisará de uma instrução switch. Sério, eu diria que depende do que o código está fazendo. Se tudo o que o switch está fazendo é chamar funções (digamos), você pode passar ponteiros de função. Se é uma solução melhor , é discutível.
A linguagem é um fator importante aqui também, eu acho.
Eu acho que o que você está procurando é o Padrão de Estratégia.
Isso pode ser implementado de várias maneiras, que foram mencionadas em outras respostas a esta pergunta, como:
switch
seria bom substituir as instruções se você adicionar novos estados ou novo comportamento às instruções:
estado int; String getString () { switch (estado) { caso 0: // comportamento para o estado 0 retornar "zero"; caso 1: // comportamento para o estado 1 retornar "um"; } lançar novo IllegalStateException (); } double getDouble () { switch (this.state) { caso 0: // comportamento para o estado 0 retornar 0d; caso 1: // comportamento para o estado 1 retornar 1d; } lançar novo IllegalStateException (); }
Adicionar novo comportamento requer copiar switch
e adicionar novos estados significa adicionar outro case
a cada switch
instrução.
Em Java, você pode alternar apenas um número muito limitado de tipos primitivos cujos valores você conhece no tempo de execução. Isso apresenta um problema por si só: os estados estão sendo representados como números ou caracteres mágicos.
A correspondência de padrões e vários if - else
blocos podem ser usados, embora realmente tenham os mesmos problemas ao adicionar novos comportamentos e novos estados.
A solução sugerida por outros como "polimorfismo" é uma instância do padrão do Estado :
Substitua cada um dos estados por sua própria classe. Cada comportamento tem seu próprio método na classe:
Estado estatal; String getString () { retornar state.getString (); } double getDouble () { retornar state.getDouble (); }
Cada vez que você adiciona um novo estado, é necessário adicionar uma nova implementação da IState
interface. Em um switch
mundo, você adicionaria um case
a cada um switch
.
Cada vez que você adiciona um novo comportamento, é necessário adicionar um novo método à IState
interface e a cada uma das implementações. Essa é a mesma carga de antes, embora agora o compilador verifique se você tem implementações do novo comportamento em cada estado preexistente.
Outros já disseram que isso pode ser muito pesado, então é claro que há um ponto em que você chega onde se move de um para outro. Pessoalmente, a segunda vez que escrevo um switch é o ponto em que refatoro.
se-mais
Eu refuto a premissa de que o interruptor é inerentemente ruim.
Por que você quer? Nas mãos de um bom compilador, uma instrução switch pode ser muito mais eficiente do que blocos if / else (além de ser mais fácil de ler), e somente os maiores switches provavelmente serão acelerados se forem substituídos por qualquer tipo da estrutura de dados de pesquisa indireta.
'switch' é apenas uma construção de linguagem e todas as construções de linguagem podem ser pensadas como ferramentas para realizar um trabalho. Como nas ferramentas reais, algumas são mais adequadas a uma tarefa do que a outra (você não usaria uma marreta para colocar um gancho de imagem). A parte importante é como é definido 'fazer o trabalho'. Ela precisa ser mantida, precisa ser rápida, precisa ser dimensionada, precisa ser extensível e assim por diante.
Em cada ponto do processo de programação, geralmente há uma variedade de construções e padrões que podem ser usados: um comutador, uma sequência if-else-if, funções virtuais, tabelas de salto, mapas com ponteiros de função e assim por diante. Com a experiência, um programador saberá instintivamente a ferramenta certa a ser usada em uma determinada situação.
Deve-se supor que qualquer pessoa que mantenha ou revise o código seja pelo menos tão experiente quanto o autor original, para que qualquer construção possa ser usada com segurança.
Se a opção existe para distinguir entre vários tipos de objetos, provavelmente você está perdendo algumas classes para descrever com precisão esses objetos ou alguns métodos virtuais ...
Para C ++
Se você está se referindo a um AbstractFactory, acho que um método registerCreatorFunc (..) geralmente é melhor do que precisar adicionar um caso para cada declaração "nova" necessária. Em seguida, deixe todas as classes criarem e registrarem uma função creator (..) que pode ser facilmente implementada com uma macro (se ouso mencionar). Eu acredito que essa é uma abordagem comum que muitos frameworks fazem. Vi pela primeira vez no ET ++ e acho que muitas estruturas que exigem uma macro DECL e IMPL o utilizam.
Em uma linguagem processual, como C, o switch será melhor do que qualquer uma das alternativas.
Em uma linguagem orientada a objetos, quase sempre existem outras alternativas disponíveis que utilizam melhor a estrutura do objeto, particularmente o polimorfismo.
O problema com as instruções do comutador surge quando vários blocos de comutadores muito semelhantes ocorrem em vários locais do aplicativo e o suporte a um novo valor precisa ser adicionado. É muito comum um desenvolvedor esquecer de adicionar suporte ao novo valor a um dos blocos de switch espalhados pelo aplicativo.
Com o polimorfismo, uma nova classe substitui o novo valor e o novo comportamento é adicionado como parte da adição da nova classe. O comportamento nesses pontos de comutação é herdado da superclasse, substituído para fornecer um novo comportamento ou implementado para evitar um erro do compilador quando o super método é abstrato.
Onde não existe um polimorfismo óbvio, pode valer a pena implementar o padrão da estratégia .
Mas se sua alternativa for um grande bloco IF ... THEN ... ELSE, esqueça-o.
Os ponteiros de função são uma maneira de substituir uma enorme declaração de switch robusta; eles são especialmente bons em idiomas onde você pode capturar funções por seus nomes e fazer coisas com elas.
Obviamente, você não deve forçar as instruções do switch a sair do seu código, e sempre há uma chance de você estar fazendo tudo errado, o que resulta em trechos redundantes de código estúpidos. (Isso é inevitável às vezes, mas um bom idioma deve permitir que você remova a redundância enquanto permanece limpo.)
Este é um ótimo exemplo de divisão e conquista:
Digamos que você tenha algum intérprete.
switch(*IP) {
case OPCODE_ADD:
...
break;
case OPCODE_NOT_ZERO:
...
break;
case OPCODE_JUMP:
...
break;
default:
fixme(*IP);
}
Em vez disso, você pode usar isto:
opcode_table[*IP](*IP, vm);
... // in somewhere else:
void opcode_add(byte_opcode op, Vm* vm) { ... };
void opcode_not_zero(byte_opcode op, Vm* vm) { ... };
void opcode_jump(byte_opcode op, Vm* vm) { ... };
void opcode_default(byte_opcode op, Vm* vm) { /* fixme */ };
OpcodeFuncPtr opcode_table[256] = {
...
opcode_add,
opcode_not_zero,
opcode_jump,
opcode_default,
opcode_default,
... // etc.
};
Observe que não sei como remover a redundância da tabela opcode_table em C. Talvez eu deva fazer uma pergunta sobre isso. :)
A resposta mais óbvia, independente da linguagem, é usar uma série de 'se'.
Se o idioma que você está usando tiver ponteiros de função (C) ou funções com valores de 1ª classe (Lua), você poderá obter resultados semelhantes a uma "opção" usando uma matriz (ou uma lista) de funções (ponteiros para).
Você deve ser mais específico no idioma, se quiser melhores respostas.
As instruções de chave geralmente podem ser substituídas por um bom design de OO.
Por exemplo, você tem uma classe de conta e está usando um extrato de opção para executar um cálculo diferente com base no tipo de conta.
Eu sugeriria que isso fosse substituído por várias classes de contas, representando os diferentes tipos de conta, e todas implementando uma interface de Conta.
A mudança se torna desnecessária, pois você pode tratar todos os tipos de contas da mesma forma e, graças ao polimorfismo, o cálculo apropriado será executado para o tipo de conta.
Depende do motivo pelo qual você deseja substituí-lo!
Muitos intérpretes usam 'gotos computados' em vez de instruções de chave para execução de código de operação.
O que sinto falta no switch C / C ++ é o Pascal 'in' e os intervalos. Eu também gostaria de poder ativar as cordas. Mas estes, embora sejam triviais para um compilador comer, são trabalhosos quando feitos usando estruturas, iteradores e outras coisas. Então, pelo contrário, há muitas coisas que eu gostaria de substituir por um switch, se apenas o switch de C () fosse mais flexível!
O switch não é um bom caminho a percorrer, pois quebra o Principal Principal Aberto. É assim que eu faço.
public class Animal
{
public abstract void Speak();
}
public class Dog : Animal
{
public virtual void Speak()
{
Console.WriteLine("Hao Hao");
}
}
public class Cat : Animal
{
public virtual void Speak()
{
Console.WriteLine("Meauuuu");
}
}
E aqui está como usá-lo (usando seu código):
foreach (var animal in zoo)
{
echo animal.speak();
}
Basicamente, o que estamos fazendo é delegar a responsabilidade à classe filho, em vez de pedir aos pais que decidam o que fazer com os filhos.
Você também pode ler sobre o "Princípio de substituição de Liskov".
Em JavaScript, usando matriz associativa:
this:
function getItemPricing(customer, item) {
switch (customer.type) {
// VIPs are awesome. Give them 50% off.
case 'VIP':
return item.price * item.quantity * 0.50;
// Preferred customers are no VIPs, but they still get 25% off.
case 'Preferred':
return item.price * item.quantity * 0.75;
// No discount for other customers.
case 'Regular':
case
default:
return item.price * item.quantity;
}
}
torna-se o seguinte:
function getItemPricing(customer, item) {
var pricing = {
'VIP': function(item) {
return item.price * item.quantity * 0.50;
},
'Preferred': function(item) {
if (item.price <= 100.0)
return item.price * item.quantity * 0.75;
// Else
return item.price * item.quantity;
},
'Regular': function(item) {
return item.price * item.quantity;
}
};
if (pricing[customer.type])
return pricing[customer.type](item);
else
return pricing.Regular(item);
}
Outro voto para if / else. Eu não sou um grande fã de declarações de caso ou troca, porque existem algumas pessoas que não as usam. O código é menos legível se você usar maiúsculas ou minúsculas. Talvez não seja menos legível para você, mas para aqueles que nunca precisaram usar o comando.
O mesmo vale para fábricas de objetos.
Os blocos if / else são uma construção simples que todos recebem. Há algumas coisas que você pode fazer para garantir que não cause problemas.
Em primeiro lugar - não tente recuar as declarações mais de um par de vezes. Se você está se identificando, está fazendo errado.
if a = 1 then
do something else
if a = 2 then
do something else
else
if a = 3 then
do the last thing
endif
endif
endif
É muito ruim - faça isso em seu lugar.
if a = 1 then
do something
endif
if a = 2 then
do something else
endif
if a = 3 then
do something more
endif
Otimização seja condenada. Não faz muita diferença na velocidade do seu código.
Em segundo lugar, não sou avesso a interromper um bloco If, desde que haja instruções de interrupção suficientes espalhadas pelo bloco de código específico para torná-lo óbvio
procedure processA(a:int)
if a = 1 then
do something
procedure_return
endif
if a = 2 then
do something else
procedure_return
endif
if a = 3 then
do something more
procedure_return
endif
end_procedure
EDIT : No Switch e por que eu acho difícil grunhir:
Aqui está um exemplo de uma instrução switch ...
private void doLog(LogLevel logLevel, String msg) {
String prefix;
switch (logLevel) {
case INFO:
prefix = "INFO";
break;
case WARN:
prefix = "WARN";
break;
case ERROR:
prefix = "ERROR";
break;
default:
throw new RuntimeException("Oops, forgot to add stuff on new enum constant");
}
System.out.println(String.format("%s: %s", prefix, msg));
}
Para mim, a questão aqui é que as estruturas de controle normais que se aplicam em linguagens como C foram completamente quebradas. Existe uma regra geral de que, se você quiser colocar mais de uma linha de código dentro de uma estrutura de controle, use chaves ou uma instrução begin / end.
por exemplo
for i from 1 to 1000 {statement1; statement2}
if something=false then {statement1; statement2}
while isOKtoLoop {statement1; statement2}
Para mim (e você pode me corrigir se eu estiver errado), a instrução Case lança essa regra pela janela. Um bloco de código executado condicionalmente não é colocado dentro de uma estrutura de início / fim. Por isso, acredito que Case seja conceitualmente diferente o suficiente para não ser usado.
Espero que responda às suas perguntas.