消除在代码中使用switch的方法有哪些?
消除在代码中使用switch的方法有哪些?
Answers:
开关语句本身并不是反模式,但是如果您要编码面向对象,则应考虑使用多态性而不是使用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();
}
typeof
,并且此答案没有建议在其他情况下解决switch语句的方法或原因。
请参阅切换语句的气味:
通常,类似的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();
}
}
如果您的(伪)代码如下所示:
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();
}
}
希望这可以帮助。
Map<Integer, Command>
不需要切换吗?
开关是一种模式,无论是否使用switch语句,链,查找表,oop多态性,模式匹配或其他方式实现。
您是否要取消使用“ switch语句 ”或“ switch模式 ”?仅当可以使用其他模式/算法时,才可以消除第一个,而第二个则可以消除,并且在大多数情况下这是不可能的,或者不是更好的方法。
如果要从代码中删除switch语句,首先要问的问题是在什么地方消除switch语句并使用其他技术。不幸的是,这个问题的答案是特定领域的。
请记住,编译器可以进行各种优化来切换语句。因此,例如,如果您想高效地进行消息处理,那么执行switch语句就差不多了。但是,另一方面,基于switch语句运行业务规则可能不是最佳方法,应重新构造应用程序。
这是switch语句的一些替代方法:
我认为最好的方法是使用一张好的Map。使用字典,您几乎可以将任何输入映射到其他值/对象/功能。
您的代码看起来像这样(伪):
void InitMap(){
Map[key1] = Object/Action;
Map[key2] = Object/Action;
}
Object/Action DoStuff(Object key){
return Map[key];
}
switch
如果您发现自己在语句中添加了新状态或新行为,则最好替换这些语句:
int状态 字符串getString(){ 开关(状态){ 案例0://状态0的行为 返回“零”; 情况1://状态1的行为 返回“一个”; } 抛出新的IllegalStateException(); } double getDouble(){ 切换(this.state){ 案例0://状态0的行为 返回0d; 情况1://状态1的行为 返回1天; } 抛出新的IllegalStateException(); }
添加新的行为需要复制switch
,并增加新的状态意味着添加另一case
对每一个 switch
发言。
在Java中,您只能切换数量非常有限的原始类型,这些原始类型在运行时知道其值。这本身就是一个问题:状态被表示为幻数或字符。
if - else
可以使用模式匹配和多个块,尽管在添加新行为和新状态时确实存在相同的问题。
其他人建议的“多态性”解决方案是State模式的一个实例:
将每个州替换为其自己的类。每个行为在类上都有自己的方法:
IState状态; 字符串getString(){ 返回state.getString(); } double getDouble(){ 返回state.getDouble(); }
每次添加新状态时,都必须添加IState
接口的新实现。在一个switch
世界中,您将为case
每个添加一个switch
。
每次添加新行为时,都需要向IState
接口和每个实现中添加新方法。尽管现在编译器将检查您是否在每个预先存在的状态上实现了新行为,但这和以前一样负担重。
其他人已经说过,这可能太重了,因此,当然有一个点,您可以从一个位置移到另一个位置。就个人而言,我第二次编写开关是重构的关键。
“切换”只是一种语言结构,所有语言结构都可以视为完成工作的工具。与实际工具一样,某些工具比另一种更适合于一项任务(您不会使用大铁锤来放置图片挂钩)。重要的部分是如何定义“完成工作”。它是否需要维护,是否需要快速,是否需要扩展,是否需要可扩展等等。
在编程过程的每个点上,通常都可以使用一系列构造和模式:开关,if-else-if序列,虚函数,跳转表,带有函数指针的映射等。经验丰富的程序员将本能地知道在给定情况下使用的正确工具。
必须假定维护或审阅代码的任何人至少与原始作者一样熟练,以便可以安全地使用任何构造。
如果使用开关来区分各种对象,则可能会缺少一些类来精确描述这些对象,或者缺少一些虚方法。
在像C这样的过程语言中,那么switch会比任何其他选择都要好。
在面向对象的语言中,几乎总是有其他替代方法可以更好地利用对象结构,尤其是多态。
当在应用程序中的多个位置出现多个非常相似的开关块时,switch语句就会出现问题,并且需要添加对新值的支持。对于开发人员来说,很常见的做法是忘记将新值的支持添加到分散在应用程序周围的一个开关块中。
通过多态,新类将替换新值,并且新行为将作为添加新类的一部分进行添加。然后,可以从超类继承这些开关点处的行为,或者对其进行重写以提供新的行为,或者在超级方法抽象时实现该功能以避免编译器错误。
在没有明显的多态性发生的地方,很值得实施Strategy模式。
但是,如果您的选择是大IF ... THEN ... ELSE块,那么就算了。
函数指针是替换庞大的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的冗余。也许我应该对此提出疑问。:)
切换不是一个好方法,因为它破坏了打开关闭委托人。这就是我的方法。
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中
:
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);
}
如果/否则则再次投票。我不是大小写或switch语句的忠实拥护者,因为有些人不使用它们。如果使用大小写或开关,则代码的可读性较差。也许您对它的可读性不是那么低,但是对于那些从来不需要使用该命令的人来说,可读性就不那么高。
对象工厂也是如此。
if / 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
优化该死。它对代码速度没有太大的影响。
其次,只要在特定代码块中散布了足够的breaks语句以使其明显,我就不反对打破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语言的普通控制结构已被完全破坏。有一条通用规则,如果要在控件结构中放置多行代码,则应使用花括号或begin / end语句。
例如
for i from 1 to 1000 {statement1; statement2}
if something=false then {statement1; statement2}
while isOKtoLoop {statement1; statement2}
对于我来说(如果我错了,您可以纠正我),Case语句将此规则抛到了窗外。有条件执行的代码块未放置在begin / end结构内。因此,我认为Case在概念上有很大不同,因此无法使用。
希望能回答您的问题。