重构Java代码,大型if语句的替代方案

11

我正在重构一个项目中的代码,遇到了一个大型 if/else if 语句,格式如下:

if (changer instanceof AppleChanger)
{
   panel = new ApplePanel();
}
else if (changer instanceof OrangeChanger)
{
   panel = new OrangePanel();
} 

现在我的第一个冲动是使用多态进行重构,使其看起来像

panel = changer.getChangerPanel();

不幸的是,类包没有访问面板包的权限。

我的下一个冲动是创建一个PanelChooser类,其中包含一个重载方法:

PanelChooser.getPanel(changer);

//Overloaded Method
public Panel getPanel(OrangeChanger changer)
{
   Panel orangePanel = new OrangePanel();
   return orangePanel;
}
public Panel getPanel(AppleChanger changer)
{
   Panel applePanel = new ApplePanel();
   return applePanel;
}

这是一个好的解决方案吗?还是有更好的方法来解决这个问题?


1
我怀疑第二个方法不会起作用,因为你必须显式地将changer转换为Java中的子类,以便识别要调用的方法。也许你可以使用一个注册表类/映射(将changer映射到面板并使用传递的实例进行查找)。 - aishwarya
4
为什么你们的课程名称都以小写字母开头? - RokL
@U Mad Thanks,是我打错字了,已经修复。 - FooBar
9个回答

13

这里的根本“问题”是你有并行的类层次结构。如果不进行一些相当大的重构,就无法替换该if语句。一些建议可以在c2 wiki上找到。

你所能做的最好的办法,可能也是一个完全可行的解决方案,是将if语句移动到一个“工厂”类中,并确保它在其他任何地方都没有重复。


1
+1 用于识别并行类层次结构。绝对是工厂模式的一个案例。 - earcam
1
工厂模式是最佳解决方案,它为您提供了一些额外的灵活性(例如允许从更改器到面板的多对一映射),将逻辑与实际类分离,并将“配置”集中在一个地方。+1 - Viruzzo
目前看来这似乎是最好的想法,我认为重构到必要的程度已经超出了我的专业水平,使用一个工厂类并附上未来可能性的注释将满足我的当前水平。 - FooBar
我强烈建议使用Google Guice(http://code.google.com/p/google-guice/)而不是工厂。 - avanderw

4
我认为你的第一反应失败是好事 :) 否则,你会把你的逻辑代码和UI代码(面板)结合起来,这是错误的。
现在我可以给你提供以下解决方案:
创建一个接口PanelCreator,其中包含方法Panel createPanel,如下所示:
interface PanelCreator {
   Panel createPanel();
}

现在提供两种实现:

public class OrangePanelCreator implements PanelCreator{
   Panel createPanel() {
        return new OrangePanel();
   }
}

public class ApplePanelCreator implements PanelCreator {

  Panel createPanel() {
        return new ApplePanel();
  }
}

现在进入有趣的部分:

创建一个Map,PanelCreator>,它将充当面板的注册表:

Map<Class<Changer>, PanelCreator> registry = new HashMap<>;
registry.put(OrangeChanger.class, new OrangePanelCreator());
registry.put(AppleChanger.class, new ApplePanelCreator());

现在你可以在你的代码中执行以下操作:

panel = registry.get(changer.getClass()).createPanel();

我认为这样更加优雅,因为您可以轻松地改变创建者的实现方式。

希望这能有所帮助。


一个好的快速解决方案,但如果有人添加了例如OrangeChanger的子类,它将无法工作。 - artbristol
在这种情况下,您需要使用额外的“put”调用来更新注册表吗? - Mark Bramnik
你可以这样做,但是你违反了Liskov替换原则。从代码行数的角度来看,与if语句相比,并没有任何优势,因为它们都可以达到同样的结果。 - artbristol
理论上你是对的,但问题在于创建这样一个子类型是否合理。你也是对的,当前实现没有任何优势,但主要思路是将面板的条件创建逻辑与更改器解耦。在实际情况下,最佳设计可能不是简单的映射,而可能是一些更高级的配置(可能基于 Spring 的 XML)。在这种情况下,优势是明显的,一旦创建了新的更改,您就不需要重新编译代码。 - Mark Bramnik
关于代码行数的测量,抱歉,我不太认同。如果你需要更少的代码行,你可以考虑使用Groovy或Scala,其中闭包可以动态地完成相同的工作,而无需像在Java中被迫创建整个类层次结构... - Mark Bramnik
如果您添加另一种实现,无论是if-else、注册表还是多态方式,都必须修改代码/配置才能使此查找起作用。一个现有实现的子类也不会有任何影响。使用注册表方法明显有优势,行数显著减少,而且圆形复杂度也大幅“表面”降低。我说“表面”是因为n路径实际上并没有减少,但它们通过配置被隐藏了。总之,这使得代码更简单易读,与此同时也更易于理解,以我个人的看法。 - aishwarya

2

如果代码中存在多个if/else结构,并且根据Changer实例类型的不同而有所不同,您可以像这样使用访问者模式:

public interface ChangerVisitor {
  void visit(OrangeChanger changer);
  void visit(AppleChanger changer);
  ...
}

public class ChangerVisitorEnabler<V extends ChangerVisitor> {
  public static <V extends ChangerVisitor> ChangerVisitorEnabler<V> enable(V) {
    return new ChangerVisitorEnabler<V>(visitor);
  }

  private final V visitor;

  private ChangerVisitorEnabler(V visitor) {
    this.visitor = visitor;
  }

  public V visit(Charger changer) {
    if (changer instanceof OrangeChanger) {
      visitor.visit((OrangeChanger)changer);
    } else if (changer instanceof AppleChanger) {
      visitor.visit((AppleChanger)changer);
    } else {
      throw new IllegalArgumentException("Unsupported charger type: " + changer);
    }
    return visitor;
  }
}

现在你有一个单一的类型检查代码块和一个类型安全接口:
public PanelChooser implements ChangerVisitor {

  public static Panel choosePanel(Changer changer) {
    return ChangerVisitorEnabler.enable(new PanelChooser()).visit(changer).panel;
  }

  private Panel panel;

  private PanelChooser() {
  }

  void visit(OrangeChanger changer) {
    panel = orangePanel();
  }

  void visit(AppleChanger changer) {
    panel = applePanel();
  }

}

使用非常简单:
panel = PanelChooser.choosePanel(chooser);

1

也许你可以这样做:

public Panel getPanel(Changer changer)
{
    String changerClassName = changer.class.getName();
    String panelClassName = changerClassName.replaceFirst("Changer", "Panel");
    Panel panel = (Panel) Class.forName(panelClassName).newInstance();
    return panel;
}

我不会用Java编程,但如果这是C#,我会尝试一下。我也不知道这是否适用于你的软件包。

祝你好运!


1
好的,但这需要为您的类定义命名规范;每个 xyzChanger 类必须有一个相应的 xyzPanel。而且它并不是真正的类型安全,因为那里有一个对 Panel 的强制转换。 - Jesper
2
在我看来,最好避免在类名中进行这样的操作,除非这是唯一的选择。这样做会更难维护,例如类名更改不会导致任何编译错误。 - Wizart
1
是的,这将是一种命名约定。约定旨在加快开发速度。但是,如果您想更改某些“Changer”类型的约定,则应该可以覆盖getPanel。这将是抽象工厂设计模式,以防您想知道。 - Daniel Lidström
这很有趣,我会搜索一下是否可以在Java中实现它,谢谢。 - FooBar
那应该已经是Java了,除非我在哪里犯了错 :-) - Daniel Lidström

0

我没有看到足够的现有代码和设计。因此,可能首先我会尝试将带面板实例化的代码移动到创建Changer实例的同一位置。因为选择一个Panel就像选择一个Changer一样。

如果选择的Changer是动态选择的,您可以只创建这些面板,然后相应地显示/隐藏它们。


更改器是通过调用服务器进行分配的,服务器无法访问面板代码,所以不幸的是这不是一个选项。 - FooBar

0

看一下工厂抽象工厂模式。

工厂模式是一种创建型模式,因为它用于控制类的实例化。工厂模式用于替换类构造函数,抽象对象生成过程,以便在运行时确定实例化的对象类型。

抽象工厂模式是一种创建型模式,因为它用于控制类的实例化。抽象工厂模式用于为客户端提供一组相关或依赖对象。由工厂创建的对象族根据具体工厂类的选择在运行时确定。


0
我会这样做:
创建一个接口PanelChooser,其中包含一个返回Changer面板的方法。
创建一个实现类ClassBasedPanelChooser,当Change实现某个类时返回一个面板,否则返回null。该类和要返回的面板在构造函数中传递。
创建另一个实现类CascadingPanelChooser,它在构造函数参数中接受一个PanelChoosers列表,并在调用其方法时询问每个PanelChooser提供面板,直到收到非空面板为止,然后返回该面板。

0

你的解决方案不会起作用,因为Java基于编译时类型选择方法(这里可能是Changer)。您可以使用Map<Class<? extends Changer>, Panel>(或者如果您需要每次创建新实例,则使用Map<Class<? extends Changer>, Class<? extens Panel>>)。如果您需要此解决方案适用于尚未知的子类,例如OrangeChanger,则需要进行额外的工作。

例如,对于每个Changer子类的单个实例

changerToPanel.get(changer.getClass());

或者如果您需要新的实例:

changerToPanelClass.get(changer.getClass()).newInstance();

另一个选择是坚持你最初的直觉,让Changer知道Panel的存在。

0

不要使用instanceof为什么多态失败 唯一可以使用instanceof的地方是在equals方法内部。

回答你的问题,请参考链接。 感谢Cowanjordao

  • 使用反射。
public final class Handler {
  public static void handle(Object o) {
    for (Method handler : Handler.class.getMethods()) {
      if (handler.getName().equals("getPanel") && 
          handler.getParameterTypes()[0] == o.getClass()) {
        try {
          handler.invoke(null, o);
          return;
        } catch (Exception e) {
          throw new RuntimeException(e);
        }
      }
    }
    throw new RuntimeException("Can't handle");
  }
  public static void handle(Apple num) { /* ... */ }
  public static void handle(Orange num) { /* ... */ }
  • 责任链模式
 public abstract class Changer{
   private Changer next;

   public final boolean handle(Object o) {
      boolean handled = doHandle(o);
      if (handled) { return true; }
      else if (next == null) { return false; }
      else { return next.handle(o); }
   }

   public void setNext(Changer next) { this.next = next; }

   protected abstract boolean doHandle(Object o);
}

public class AppleHandler extends Changer{
   @Override
   protected boolean doHandle(Object o) {
      if (!o instanceof Apple ) {
         return false;
      }
      OrangeHandler.handle((Orange) o);
      return true;
   }
}

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