重构if / else逻辑

时间:2010-05-26 13:44:27

标签: java refactoring if-statement

我有一个带有千行if / else逻辑方法的java类,如下所示:

if (userType == "admin") {
     if (age > 12) {
          if (location == "USA") {
               // do stuff
          } else if (location == "Mexico") {
               // do something slightly different than the US case
          }
     } else if (age < 12 && age > 4) {
          if (location == "USA") {
               // do something slightly different than the age > 12 US case
          } else if (location == "Mexico") {
               // do something slightly different
          }
     }
 } else if (userType == "student") {
     if (age > 12) {
          if (location == "USA") {
               // do stuff
          } else if (location == "Mexico") {
               // do something slightly different than the US case
          }
     } else if (age < 12 && age > 4) {
          if (location == "USA") {
               // do something slightly different than the age > 12 US case
          } else if (location == "Mexico") {
               // do something slightly different
          }
     }

我应该如何将其重构为更易于管理的内容?

14 个答案:

答案 0 :(得分:26)

您应该使用Strategies,可能在枚举中实现,例如:

enum UserType {
  ADMIN() {
    public void doStuff() {
      // do stuff the Admin way
    }
  },
  STUDENT {
    public void doStuff() {
      // do stuff the Student way
    }
  };

  public abstract void doStuff();
}

由于代码中每个最外层if分支内的代码结构看起来几乎相同,因此在下一步重构时,您可能希望使用template methods来分解该重复。或者,您也可以将位置(可能还有年龄)转换为策略。

在Java4中,您可以手动实现typesafe enum,并使用普通的旧子类来实现不同的策略。

答案 1 :(得分:12)

我要对此代码执行的第一件事是创建类型AdminStudent,这两种类型都继承自基类型User。这些类应该有doStuff()方法,您可以隐藏其余逻辑。

根据经验,只要你自己开启类型,就可以使用多态。

答案 2 :(得分:9)

千?也许你需要一个规则引擎。 Drools可能是一个可行的选择。

或者一个命令模式,它封装了每个案例的所有“做一些稍微不同”的逻辑。将每个命令存储在地图中,并将年龄,位置和其他因素作为关键字连接起来。查找命令,执行它,你就完成了。很干净。

Map可以存储为配置并在启动时读入。您可以通过添加新类和重新配置来添加新逻辑。

答案 3 :(得分:6)

首先 - 为userType和location使用枚举 - 然后你可以使用switch语句(提高可读性)

第二 - 使用更多方法。

示例:

switch (userType) {
  case Admin: handleAdmin(); break;
  case Student: handleStudent(); break;
}

以后

private void handleAdmin() {
  switch (location) {
    case USA: handleAdminInUSA(); break;
    case Mexico: handleAdminInMexico(); break;
  }
}

此外,识别重复的代码并将其放入额外的方法中。

修改

如果有人强迫您在没有枚举的情况下编写Java代码(就像您被迫使用Java 1.4.2一样),请使用'final static'而不是enums或执行以下操作:

  if (isAdmin(userType)) {
    handleAdmin(location, age);
  } else if (isStudent(userType)) {
    handleStudent(location, age));
  }

//...

private void handleAdmin(String location, int age) {
  if (isUSA(location)) {
    handleAdminInUSA(age);
  } else if (isUSA(location)) {
    handleAdminInMexico(age);
  }
}

//...

private void handleAdminInUSA(int age) {
  if (isOldEnough(age)) {
    handleAdminInUSAOldEnough();
  } else if (isChild(age)) {
    handleChildishAdminInUSA(); // ;-)
  } //...
}

答案 4 :(得分:2)

这样做的风险不仅在于它是不雅观的,而且非常容易出错。过了一段时间,您可能会遇到重叠的风险。

如果能够通过用户类型真正区分条件,则可以将每个条件的主体分解为单独的函数。因此,您可以根据类型进行检查,并调用特定于该类型的相应函数。更OO的解决方案是将每个用户表示为一个类,然后覆盖一些计算方法以根据年龄返回一个值。如果你不能使用类但至少可以使用枚举,那么你将能够在枚举上做一个更好的switch语句。字符串上的开关只有Java 7。

让我担心的是重叠的情况(例如,具有一些共享规则的两种用户类型等)。如果最终出现这种情况,您可能最好将数据表示为您将要读取和维护的某个外部文件(例如,表),并且您的代码本质上将作为在此数据中执行相应查找的驱动程序运行组。这是复杂业务规则的常用方法,因为没有人愿意去维护大量代码。

答案 5 :(得分:2)

我可能首先检查一下你是否可以参数化doStuff和doSimilarStuff代码。

答案 6 :(得分:2)

您可以使用责任链模式。

if-else语句重构为具有接口IUserController的类。

在列表或树或任何合适的数据结构中初始化您的链,并在此链中执行所需的功能。您可以使用Builder模式创建提到的数据结构。它类似于策略模式,但在责任链模式中,链中的实例可以调用链接的实例。

此外,您可以使用策略模式为特定于位置的功能建模。希望能帮助到你。

答案 7 :(得分:2)

如果块中的代码符合几个标准模式,我将创建一个包含列(type,location,minAge,maxAge,action)的表,其中'action'是一个枚举,指示几种类型的处理中的哪一种做。理想情况下,此表将从数据文件中读取或保存在SQL中。

然后,您可以在Java代码中执行表查找,以确定要为用户执行的操作。

答案 8 :(得分:1)

您可以将userType设为enum,并为其提供一种执行所有“稍微不同的”操作的方法。

答案 9 :(得分:1)

没有更多信息没有好的答案

但是公平的猜测是这样的:使用OO

首先定义一个User,定义Admin,Student和所有其他类型的用户,然后让polymorphism处理剩下的事情

答案 10 :(得分:1)

仅基于变量名称,我猜你应该将User(或其中包含userType变量的内容)子类化为AdminUserStudentUser (可能还有其他人)并使用polymorphism

答案 11 :(得分:1)

查看访客模式。它利用了多态性,但稍微更灵活一点,以后更容易添加新案例。

缺点是您需要某种方式将状态信息转换为不同的实例。这样做的好处是可以更轻松地添加行为,而无需修改继承层次结构。

答案 12 :(得分:1)

你真的需要将这些案例分解为对象方法。我假设这些字符串和数字被从数据库中拉出来。您需要使用这些数据来构建对所需交互进行建模的对象,而不是在巨型嵌套条件逻辑中以原始形式使用它们。考虑具有StudentRole和AdminRole子类的UserRole类,具有USA和墨西哥子类的Region类,以及具有适当分区子类的AgeGroup类。

一旦你有了这个面向对象的结构,你就可以利用易于理解的面向对象的设计模式来重新考虑这个逻辑。

答案 13 :(得分:0)

使用OOP概念: 这取决于设计的其余部分,但也许你应该有一个user界面,StudentAdmin接口扩展它和UsaStudentMexicoStudentUsaAdminMexicoAdmin实现做一些事情。保留User个实例,然后调用其doStuff方法。