消除代码切换的方法

有哪些方法可以消除在代码中使用开关?

162099 次浏览

每个人都喜欢巨大的 if else街区。这么容易看懂!不过,我很好奇为什么要删除 switch 语句。如果需要 switch 语句,则可能需要 switch 语句。说真的,这取决于代码的作用。如果所有的开关都在调用函数(比如说) ,那么可以传递函数指针。是否是 好多了解决方案是有争议的。

我认为语言也是一个重要的因素。

我认为最好的方法是使用一个好的地图。使用字典,您可以将几乎任何输入映射到其他值/对象/函数。

你的代码看起来像这样:

void InitMap(){
Map[key1] = Object/Action;
Map[key2] = Object/Action;
}


Object/Action DoStuff(Object key){
return Map[key];
}

如果-否则

不过,我反驳了转变本质上是不好的这一前提。

首先,我不知道使用开关是反模式的。

其次,switch 总是可以替换为 if/else if 语句。

最明显的,与语言无关的答案是使用一系列的“如果”。

如果你正在使用的语言有函数指针(C)或者有函数是第一类值(Lua) ,你可以使用函数的数组(或者列表)来实现类似于“开关”的结果。

如果你想要更好的答案,你应该在语言上更具体一些。

为什么?在一个好的编译器手中,switch 语句可以比 if/else 块更有效(也更容易阅读) ,只有最大的开关可能会被任何类型的间接查找数据结构所替代,从而提高速度。

切换本身并没有那么糟糕,但是如果你的方法中对象有很多“切换”或者“如果/否”,这可能表明你的设计有点“过程性”,你的对象只是值桶。将逻辑移动到对象,调用对象上的方法,让它们决定如何响应。

“ switch”只是一种语言结构,所有的语言结构都可以被认为是完成工作的工具。与真正的工具一样,有些工具更适合于一项任务而不是另一项任务(你不会用大锤来挂画钩)。重要的是如何定义“完成工作”。它是否需要可维护,是否需要快速,是否需要扩展,是否需要可扩展等等。

在编程过程的每个点上,通常都有一系列可以使用的构造和模式: 开关、 if-else-if 序列、虚函数、跳转表、带函数指针的映射等等。有了经验,程序员会本能地知道在给定的情况下应该使用什么样的工具。

必须假定任何维护或审查代码的人至少和原始作者一样熟练,这样才能安全地使用任何构造。

如果切换是为了区分不同类型的对象,那么您可能会缺少一些类来精确描述这些对象,或者缺少一些虚方法..。

Switch 是一种模式,无论是通过 switch 语句、 if else 链、查找表、 oop 多态性、模式匹配还是其他什么实现的。

你想要取消使用“ 开关语句”还是“ 开关模式”?只有在可以使用另一种模式/算法的情况下,才能消除第一种模式/算法,而且大多数情况下这是不可能的,或者这不是一种更好的方法。

如果要从代码中消除 开关语句,首先要问的问题是 消除 switch 语句在哪里有意义并使用其他一些技术。不幸的是,这个问题的答案是特定于领域的。

请记住,编译器可以对切换语句进行各种优化。因此,例如,如果您想要有效地处理消息,一个 switch 语句就是很好的方法。但另一方面,基于 switch 语句运行业务规则可能不是最好的方法,应该重新构建应用程序。

下面是一些切换语句的替代方法:

  • 翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳
  • 多态性
  • 模式匹配(特别用于函数式编程,C + + 模板)

Switch 语句通常可以被一个好的 OO 设计所替代。

例如,您有一个 Account 类,并且正在使用 switch 语句根据帐户类型执行不同的计算。

我建议用一些帐户类来代替它,这些类代表不同类型的帐户,并且所有类都实现了 Account 接口。

然后就不需要切换了,因为您可以对所有类型的帐户一视同仁,并且由于多态性,将对帐户类型运行适当的计算。

Switch 语句本身并不是一个反模式,但是如果您正在编写面向对象的代码,那么您应该考虑是否用 多态性代替 switch 语句更好地解决了交换机的使用问题。

通过多态性,这样:

foreach (var animal in zoo) {
switch (typeof(animal)) {
case "dog":
echo animal.bark();
break;


case "cat":
echo animal.meow();
break;
}
}

变成了这样:

foreach (var animal in zoo) {
echo animal.speak();
}

我认为你要寻找的是战略模式。

这可以通过若干方式实现,对这个问题的其他答复中也提到了这一点,例如:

  • 值-> 函数的映射
  • 多态性(对象的子类型将决定它如何处理特定的进程)。
  • 一级函数。

另一个投票,如果/否则。我不是一个案例或转换陈述的超级粉丝,因为有些人不使用它们。如果使用 case 或 switch,则代码的可读性较差。也许对您来说不是不易读,而是对那些从来不需要使用命令的人来说。

对象工厂也是如此。

If/else 块是每个人都会得到的一个简单构造。有一些事情你可以做,以确保你不会造成问题。

首先——不要尝试多次缩进 if 语句。如果你发现自己在缩进,那么你就做错了。

 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

真的很糟糕-这样做,而不是。

if a = 1 then
do something
endif
if a = 2 then
do something else
endif
if a = 3 then
do something more
endif

让优化见鬼去吧,它对你的代码速度没有太大的影响。

其次,只要有足够多的 break 语句分散在特定的代码块中,使其显而易见,我并不反对突破 If 块

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

关于 Switch 以及为什么我认为它很难理解:

下面是 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));
}

对我来说,这里的问题是,应用于类似 C 语言的正常控制结构已经完全被打破了。如果想在控件结构中放置多行代码,一般规则是使用大括号或 start/end 语句。

例如:。

for i from 1 to 1000 {statement1; statement2}
if something=false then {statement1; statement2}
while isOKtoLoop {statement1; statement2}

对于我来说(如果我错了,您可以纠正我) ,Case 语句将这条规则抛出窗外。有条件执行的代码块不放在 start/end 结构中。正因为如此,我相信 Case 在概念上的不同足以不被使用。

希望这能回答你的问题。

参见 开关语句气味:

通常,类似的 switch 语句分散在整个程序中。如果在一个开关中添加或删除一个子句,通常还需要查找和修复其他开关。

重构重构到模式都有解决这个问题的方法。

如果您的(伪)代码看起来像:

class RequestHandler {


public void handleRequest(int action) {
switch(action) {
case LOGIN:
doLogin();
break;
case LOGOUT:
doLogout();
break;
case QUERY:
doQuery();
break;
}
}
}
这段代码违反了 开闭原理,并且对于随之而来的每一种新类型的操作代码都是脆弱的。 为了解决这个问题,您可以引入一个“ Command”对象:

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

希望这个能帮上忙。

那要看你为什么要换了!

许多解释器使用“计算 gotos”代替 switch 语句执行操作码。

关于 C/C + + 开关,我想念的是 Pascal‘ in’和 range。我也希望我能打开琴弦。但是,尽管这些对编译器来说微不足道,但是如果使用结构和迭代器之类的东西就很难完成。所以,恰恰相反,如果 C 的 switch ()更灵活一些,我希望我可以用一个开关来替换很多东西!

C + +

如果你指的是一个 AbstractFactory,我认为一个 RegisterCreatorFunc (. .)方法通常比为每一个需要的“新”语句添加一个 case 要好。然后,让所有的类创建和注册一个 creatorFunction (. .),这可以很容易地实现与宏(如果我敢提到)。我相信这是许多框架都采用的一种常见方法。我第一次看到它是在 ET + + 中,我认为许多需要 DECL 和 IMPL 宏的框架都使用它。

如果您发现自己在语句中添加了新的状态或新的行为,那么最好替换 switch语句:

int state;


String getString() {
switch (state) {
case 0 : // behaviour for state 0
return "zero";
case 1 : // behaviour for state 1
return "one";
}
throw new IllegalStateException();
}


double getDouble() {


switch (this.state) {
case 0 : // behaviour for state 0
return 0d;
case 1 : // behaviour for state 1
return 1d;
}
throw new IllegalStateException();
}

添加新行为需要复制 switch,添加新状态意味着向 每个 switch语句添加另一个 case

在 Java 中,只能切换在运行时知道其值的非常有限的基元类型。这本身就提出了一个问题: 状态被表示为神奇的数字或字符。

可以使用多个 if - else块,但在添加新行为和新状态时确实存在同样的问题。

其他人提出的“多态性”解决方案是 状态模式的一个实例:

将每个状态替换为它自己的类。每个行为在类上都有自己的方法:

IState state;


String getString() {
return state.getString();
}


double getDouble() {
return state.getDouble();
}

每次添加新状态时,都必须添加 IState接口的新实现。在 switch的世界里,你需要给每个 switch加一个 case

每次添加新行为时,都需要向 IState接口和每个实现添加一个新方法。这个负担和以前一样,不过现在编译器将检查您是否在每个预先存在的状态上实现了新行为。

其他人已经说过,这可能是太重量级,所以当然有一个点,你达到你从一个到另一个。就我个人而言,第二次编写开关是我重构的时候。

在一个像 C 这样的工作语言中,切换会比其他任何选择都要好。

在面向对象的语言中,几乎总是有其他更好地利用对象结构的替代方案,特别是多态性。

当在应用程序的多个位置出现几个非常相似的开关块时,就会出现 switch 语句的问题,并且需要添加对新值的支持。对于开发人员来说,忘记向散布在应用程序周围的开关块添加对新值的支持是很常见的。

使用多态性,然后一个新类替换新值,并添加新行为作为添加新类的一部分。然后从超类继承这些开关点上的行为,重写以提供新的行为,或者在超类方法是抽象的时候实现以避免编译器错误。

在没有明显的多态性的地方,实现 策略模式是非常值得的。

但是如果您的选择是一个大的 IF... THEN... ELSE 块,那么就忘记它。

使用一种没有内置 switch 语句的语言。

说真的,你为什么要逃避呢?如果你有充分的理由避免它,为什么不干脆避免它呢?

函数指针是替代庞大的 switch 语句的一种方法,它们在语言中特别有用,你可以通过函数的名称捕获函数并用它们创建东西。

当然,您不应该强行将 switch 语句从代码中移出,而且总是有可能完全错误地执行了这些操作,从而导致出现愚蠢的冗余代码。(有时候这是不可避免的,但是一个好的语言应该允许你在保持清洁的同时去除冗余。)

这是一个很好的分而治之的例子:

假设你有一个翻译。

switch(*IP) {
case OPCODE_ADD:
...
break;
case OPCODE_NOT_ZERO:
...
break;
case OPCODE_JUMP:
...
break;
default:
fixme(*IP);
}

相反,你可以这样做:

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

请注意,我不知道如何删除 C 中 opcode _ table 的冗余,也许我应该就此提出一个问题。:)

切换不是一个好的方法,因为它破坏了 Open Close 主体。我就是这么做的。

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

下面是如何使用它(用你的代码) :

foreach (var animal in zoo)
{
echo animal.speak();
}

基本上,我们所做的就是把责任委派给孩子们,而不是让家长来决定如何处理孩子们。

你可能还想读读“ Liskov代换原则”。

在 JavaScript 中使用关联数组: < br > 这个:

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

变成了这样:

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

礼貌