“if”语句与面向对象设计比较

7
我有一个枚举类型叫做 ErrorCodes。
public enum ErrorCodes { 
       INVALID_LOGIN(100),
       INVALID_PASSWORD(101),
       SESSION_EXPIRED(102) ...;

       private int errorCode;

       private ErrorCodes(int error){
              this.errorCode = error;
       }    //setter and getter and other codes
}

现在我用这些错误代码来检查我的异常错误。我不想写一长串的if语句,怎么解决这个问题呢(写10多个if块)?

有没有设计模式可以应对这种情况?

谢谢!


只有case语句是否足够,还是我漏掉了什么? - Jean
是的,因为这种方式每次枚举改变(例如添加新case语句)时,都需要更改你的实现。当基于良好的面向对象设计实现代码时,应尝试对扩展开放但对修改关闭。 - Mephisztoe
6个回答

7
无论是使用if语句、switch语句,还是以某种方式将逻辑实现到ErrorCode中,都可以完成任务。
在面向对象的方式中,它取决于您希望应用程序或系统如何对错误代码做出反应。比方说,您只想输出某种类型的对话框:
public doSomethingWithError() {

  ErrorCodes e = getError(); 
     // the source of error, or originator, returns the enum

  switch(e) {
    case ErrorCodes.INVALID_LOGIN:
      prompt('Invalid Login');
    case ErrorCodes.INVALID_PASSWORD:
      prompt('Invalid password');
    // and so on
  }

}

我们可以创建一个ErrorHandler类,来完成这个任务:
// We'll implement this using OO instead
public doSomethingWithError() {

  ErrorHandler e = getError(); 
    // the originator now returns an ErrorHandler object instead

  e.handleMessage();

}

// We will need the following abstract class:
public abstract class ErrorHandler {

   // Lets say we have a prompter class that prompts the message
   private Prompter prompter = new Prompter();

   public final void handleMessage() {
     String message = this.getMessage();
     prompter.prompt(message);
   } 

   // This needs to be implemented in subclasses because
   // handleMessage() method is using it.
   public abstract String getMessage();
}

// And you'll have the following implementations, e.g.
// for invalid logins:
public final class InvalidLoginHandler() {

  public final String getMessage() {
     return "Invalid login";
  }

}

// E.g. for invalid password:
public final class InvalidPasswordHandler() {
  public final String getMessage() {
    return "Invalid password";
  }
}

前一种解决方案易于实现,但随着代码规模的增长变得难以维护。后一种解决方案更复杂(即模板方法模式遵循开闭原则),但可以在需要时向ErrorHandler中添加更多方法(例如还原资源或其他)。您也可以使用策略模式来实现此操作。
您无法完全摆脱条件语句,但在后一种情况下,条件被推到发生错误的代码部分。这样,您就不会在源头和错误处理代码处有重复的条件语句维护。
编辑:

如果你希望这样做,可以参考Michael Borgwardt的答案oksayt的答案来实现Java枚举上的方法。


4

Java枚举非常强大,可以允许每个实例拥有自己的方法实现:

public enum ErrorCode { 
       INVALID_LOGIN {
        public void handleError() {
            // do something
        }
    },
       INVALID_PASSWORD {
        public void handleError() {
            // do something else
        }
    },
       SESSION_EXPIRED {
        public void handleError() {
            // do something else again
        }
    };

    public abstract void handleError();
}

那么你可以简单地调用errorCode.handleError();。然而,值得怀疑的是,ErrorCode枚举是否真正适合放置该逻辑。


1
啊,我没想到你可以在枚举中添加抽象方法。相当聪明。 - Spoike
提前感谢。我认为实现一个扩展其他接口的接口需要太多代码,所以我删除了所有内容。这种方法非常吸引人。 - user467871

3
如Spoike所指出的,使用多态来选择正确的错误处理方法是一种选择。这种方法基本上将10+的if块延迟到JVM的虚拟方法查找中,通过定义类层次结构实现。
但在采用完整的类层次结构之前,也可以考虑使用枚举方法。如果每种情况下要执行的操作非常相似,则此选项效果很好。
例如,如果您想为每个ErrorCode返回不同的错误消息,只需执行以下操作:
// Note singular name for enum
public enum ErrorCode { 
   INVALID_LOGIN(100, "Your login is invalid"),
   INVALID_PASSWORD(101, "Your password is invalid"),
   SESSION_EXPIRED(102, "Your session has expired");

   private final int code;
   private final String 

   private ErrorCode(int code, String message){
          this.code = code;
          this.message = message;
   }

   public String getMessage() {
       return message;
   }
}

那么,你的错误处理代码只需如下所示:
ErrorCode errorCode = getErrorCode();
prompt(errorCode.getMessage());

这种方法的一个缺点是,如果你想添加额外的情况,你需要修改枚举本身,而使用类层次结构则可以在不修改现有代码的情况下添加新情况。

谢谢,我解决了我的问题,只需添加这个方法:public static String getErrorMessage(int errorCode) ; 并在 ErrorCode.values 中查找消息。感谢大家! - user467871
顺便提一下,如果您目前正在每次错误代码查找时循环遍历所有元素ErrorCode.values,请查看此解决方案,它可以在常数时间内完成:https://dev59.com/WHE85IYBdhLWcg3wbS_1#2780170 - oksayt

2

我认为最好的方法是实现策略模式。这样,当添加新的枚举时,您不必更改现有类,但仍然可以扩展它们(开闭原则)。

搜索策略模式和开闭原则。


0

您可以创建一个错误码(Integer)与枚举类型的映射表。

编辑

在这个解决方案中,一旦映射表准备好,您可以在映射表中查找错误码,因此不需要进行 if..else 查找。

例如:

Map<Integer, ErrorCodes> errorMap = new HashMap<Integer, ErrorCodes>();
for (ErrorCodes error : ErrorCodes.values()) {
     errorMap.put(error.getCode(), error);
}

现在,当您想要检查来自应用程序的错误代码时,您所需要做的就是:
ErrorCodes error = errorMap.get(erro_code_from_application);

因此,消除了所有if..else的需要。

您只需要以一种使添加错误代码不需要更改其他代码的方式设置地图。地图的准备是一次性活动,并且可以在应用程序初始化期间链接到数据库、属性文件等。


我猜这不是解决方案。我的代码现在是正确的,但它没有遵守面向对象原则。我的意思是,写 if 语句:if() else if() else if() .... else() 不是一个好的解决方案。 - user467871

0

在我看来,使用ErrorCodes作为枚举类型并使用switch语句进行错误处理分发没有任何问题。枚举和switch非常搭配。

然而,也许你会觉得以下内容有趣(有点过度设计),请参见示例Wikipedia上的“双重分派”。 假设需求如下:

  • 错误处理应该封装在自己的类中
  • 错误处理应该是可替换的
  • 类型安全:每当添加一个错误时,您都必须在每个错误处理程序实现中添加错误处理。不可能在一个(或多个)switch语句中“忘记”一个错误。

代码:

//Inteface for type-safe error handler
interface ErrorHandler {
    void handleInvalidLoginError(InvalidLoginError error);
    void handleInvalidPasswordError(InvalidLoginError error);
//One method must be added for each kind error. No chance to "forget" one.
}

//The error hierachy
public class AbstractError(Exception) {
    private int code;
    abstract public void handle(ErrorHandler);
}
public class InvalidLoginError(AbstractError) {
    private String additionalStuff;
    public void handle(ErrorHandler handler) {
        handler.handleInvalidLoginError(this);
    }   
    public String getAdditionalStuff();
}
public class InvalidPasswordError(AbstractError) {
    private int code;
    public void handle(ErrorHandler handler) {
        handler.handleInvalidPasswordError(this);
    }
}

//Test class
public class Test {
    public void test() {
        //Create an error handler instance.
        ErrorHandler handler = new LoggingErrorHandler();

        try {
            doSomething();//throws AbstractError
        }
        catch (AbstractError e) {
            e.handle(handler);
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接