如何消除代码中的开关

有什么方法可以消除代码中的开关的使用?

开关语句本身不是一个反模式,但是如果你是面向对象的编码,你应该考虑如果使用多态而不是使用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(); } 

见开关语句嗅觉 :

通常,类似的开关语句分散在整个程序中。 如果您在一台交换机中添加或删除一个子句,则通常也必须查找并修复其他的子句。

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

如果您的(伪)代码如下所示:

 class RequestHandler { public void handleRequest(int action) { switch(action) { case LOGIN: doLogin(); break; case LOGOUT: doLogout(); break; case QUERY: doQuery(); break; } } } 

这段代码违反了开放封闭原则 ,对于每一种新的types的代码都是脆弱的。 为了解决这个问题,你可以引入一个'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(); } } 

如果您的(伪)代码如下所示:

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

那么你可以引入一个“状态”对象。

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

希望这可以帮助。

开关本身并不是那么糟糕,但是如果你的方法中的对象有很多的“开关”或“如果/其他”,这可能表明你的devise有点“过程化”,而你的对象只是价值桶。 把逻辑移到你的对象上,在你的对象上调用一个方法,让他们决定如何响应。

一个开关是一个模式,不pipe是用switch语句实现的,还是链,查找表,oop多态,模式匹配还是别的。

你想消除使用“ 开关语句 ”或“ 开关模式 ”? 第一个是可以被淘汰的,第二个只有在可以使用另一个模式/algorithm的情况下,大部分时间是不可能的,或者这不是一个更好的方法。

如果你想从代码中消除switch语句 ,首先要问的是在什么地方消除switch语句并使用其他技术。 不幸的是,这个问题的答案是特定领域的。

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

以下是switch语句的一些替代方法:

  • 查找表
  • 多态性
  • 模式匹配 (特别用于函数式编程,C ++模板)

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

你的代码看起来像这样(psuedo):

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

if else阻止,每个人都会喜欢巨大的。 这么容易阅读! 不过,我很好奇你为什么要删除switch语句。 如果你需要一个switch语句,你可能需要一个switch语句。 说真的,我认为这取决于代码的function。 如果所有的开关都在调用函数(比如说),你可以传递函数指针。 这是否是一个更好的解决scheme是值得商榷的。

我想这里语言也是一个重要的因素。

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

这可以通过许多方式来实现,在这个问题的其他答案中已经提到,例如:

  • 值的映射 – >函数
  • 多态性。 (对象的子types将决定它如何处理特定的过程)。
  • 一stream的function。

如果你发现自己给语句添加新的状态或新的行为,那么switch语句就可以被replace:

 int状态;

 String getString(){
   开关(状态){
     情况0://状态为0的行为
           返回“零”;
     情况1://状态1的行为
           返回“一”;
    }
   抛出新的IllegalStateException();
 }

 double getDouble(){

   开关(this.state){
     情况0://状态为0的行为
           返回0d;
     情况1://状态1的行为
           返回1d;
    }
   抛出新的IllegalStateException();
 }

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

在Java中,只能切换数量非常有限的基本types,这些基本types的值在运行时知道。 这本身就带来了一个问题:国家正在被performance为神奇的数字或字符。

可以使用模式匹配和多个if - else块,但是在添加新行为和新状态时确实有相同的问题。

其他人提出的“多态”的解决scheme是国家模式的一个例子:

用它自己的类replace每个状态。 每个行为在这个类上都有自己的方法:

 IState状态;

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

 double getDouble(){
   返回state.getDouble();
 }

每次添加新状态时,都必须添加一个IState接口的新实现。 在switch世界中,您将为每台switch添加一个case

每次添加新行为时,都需要在IState接口和每个实现中添加一个新方法。 这和以前一样,尽pipe现在编译器会检查你在每个预先存在的状态上是否有新行为的实现。

其他人已经表示,这可能是太重了,所以当然有一个点,你到达的地方,你从一个移动到另一个。 就我个人来说,第二次写开关就是我重构的地方。

如果别的

我驳斥了开关本质上是坏的前提。

那么,我不知道使用开关是一种反模式。

其次,switch总是可以用if / else if语句replace。

你为什么想要? 在一个好的编译器中,一个switch语句可以比if / else块更有效率(以及更容易阅读),只有最大的开关可能会被加速,如果它们被任何types的间接查询数据结构。

“开关”只是一种语言结构,所有的语言结构都可以被认为是完成工作的工具。 与真实的工具一样,一些工具比另一个更适合于一个任务(你不会用大锤来放置一个图片钩子)。 重要的部分是如何“完成工作”的定义。 是否需要维护,是否需要快速,是否需要扩展,是否需要扩展等等。

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

必须假定任何维护或审查代码的人都至less与原作者一样熟练,以便可以安全地使用任何构造。

如果交换机在那里区分各种对象,你可能会错过一些类来精确地描述这些对象,或一些虚拟方法…

对于C ++

如果你指的是一个AbstractFactory,我认为registerCreatorFunc(..)方法通常比为需要的每个“new”语句添加一个case要好。 然后让所有的类创build并注册一个creatorFunction(..) ,它可以很容易地用一个macros来实现(如果我敢说的话)。 我相信这是许多框架共同的做法。 我第一次在ET ++中看到它,我认为许多需要DECL和IMPLmacros的框架使用它。

使用不带有内置switch语句的语言。 Perl 5浮现在脑海中。

说真的,你为什么要避免呢? 如果你有很好的理由避免它,为什么不简单地避免呢?

函数指针是取代一个巨大的开关语句的一种方法,它们在语言中特别好,你可以通过它们的名字来捕获函数,并用它们做东西。

当然,你不应该强迫开关语句从你的代码中出来,并且总是有可能你做的都是错误的,这会导致愚蠢的冗余代码片断。 (有时这是不可避免的,但是一个好的语言应该让你在保持清洁的同时消除冗余。)

这是一个伟大的分而治之的例子:

假设你有某种口译员。

 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冗余。也许我应该提出一个问题。 🙂

最明显的,独立于语言的答案是使用一系列“if”。

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

如果你想得到更好的答案,你应该对语言更加具体。

Switch语句通常可以被一个好的OOdevise所取代。

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

我build议,这应该由多个帐户类别代替不同types的帐户,所有实现帐户界面。

然后交换机就变得不必要了,因为您可以将所有types的账户视为相同,并且由于多态性,所以将针对该账户types运行适当的计算。

取决于你为什么要取代它!

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

我对C / C ++切换的想法是Pascal'in'和范围。 我也希望我能打开string。 但是,使用结构体,迭代器和事物完成这些工作对于编译器来说是微不足道的。 所以,相反,如果只有C的开关()更灵活,我希望能用开关来代替很多东西!

在像C这样的程序语言中,切换会比其他select更好。

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

当在应用程序的多个位置出现几个非常类似的开关块时,会出现switch语句的问题,并且需要添加对新值的支持。 开发人员忘记将新值的支持添加到散布在应用程序中的开关块之一是很常见的。

如果使用多态性,则新类会replace新值,新行为将作为添加新类的一部分添加。 然后,这些切换点处的行为或者从超类inheritance,被覆盖以提供新的行为,或者当super方法是抽象的时候被实现以避免编译器错误。

在没有明显的多态性的情况下,可以很好地实施战略模式 。

但如果你的select是一个大的IF …那么…否则块,然后忘记它。

开关不是一个好办法,因为它打破了开放closures的校长。 这是我如何做到的。

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

基本上我们正在做的是把责任分配给孩子class级,而不是由父母决定如何处理孩子。

你可能也想阅读“Liskov替代原则”。

在使用关联数组的JavaScript中:
这个:

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

礼貌

如果你正在寻找

为条件创build对象( Factory_method的替代方法)

要么

根据条件执行一段代码(加载策略 )

我有一个解决scheme。

  1. 使用Reflection API:通过传递className来创build新的实例类
  2. 通过使用className作为参数:使用可能的类预先填充Map => ClassName VS对象实例。 传递className并从Map中获取相应的Object并使用它。

一个例子是devise模式( 工厂 )@

devise模式:工厂vs工厂方法与抽象工厂

另一个投票if / else。 我不是案件或交换声明的巨大粉丝,因为有些人不使用它们。 如果您使用大小写或开关,则代码的可读性较差。 对你来说可能不是那么可读,但是对于那些从来不需要使用这个命令的人来说。

对象工厂也是如此。

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

首先 – 不要尝试缩进,如果陈述超过几次。 如果你发现自己缩进,那么你做错了。

  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 

优化是该死的。 它不会对代码的速度产生太大的影响。

其次,只要在特定代码块中分散了足够的中断语句,我就不会反对突破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语句的例子

 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语言编写的正常控制结构已经被彻底打破了。 有一个通用的规则,如果你想在控制结构中放置多行代码,你可以使用大括号或开始/结束语句。

例如

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

对我来说(如果我错了,你可以纠正我),Case语句把这个规则抛出窗口。 有条件执行的代码块不放置在开始/结束结构中。 因此,我认为Case在概念上是不同的,不能被使用。

希望能回答你的问题。