【问题标题】:Refactor java code重构java代码
【发布时间】:2011-02-03 02:39:28
【问题描述】:

好吧,猜猜这个问题看起来很像:

What is the best way to replace or substitute if..else if..else trees in programs?

考虑关闭这个问题!


我想重构如下所示的代码:

String input; // input from client socket.
if (input.equals(x)) {
  doX();
} else if (input.equals(y)) {
  doY();
} else {
  unknown_command();
}

它是检查来自套接字的输入以执行某些操作的代码,但我不喜欢 if else 构造,因为每次将新命令添加到服务器(代码)时,都必须添加一个新的 if else很丑。此外,在删除命令时,if else 也必须修改。

【问题讨论】:

标签: java refactoring


【解决方案1】:

Map<String, Command> 中收集这些命令,其中Command 是带有execute() 方法的interface

Map<String, Command> commands = new HashMap<String, Command>();
// Fill it with concrete Command implementations with `x`, `y` and so on as keys.

// Then do:
Command command = commands.get(input);
if (command != null) {
    command.execute();
} else {
    // unknown command.
}

为了更进一步,您可以考虑通过扫描实现特定接口(在本例中为Command)的类或类路径中的特定注释来动态填充映射。 Google Reflections 可能会对此有所帮助。

更新(来自 cmets)您也可以考虑将 answer of Instantsoup 与我的回答结合起来。在buildExecutor() 方法中,首先从Map 中获取命令,如果Map 中不存在该命令,则尝试加载关联的类并将其放入Map。有点懒加载。这比在我的答案中扫描整个类路径并在 Instantsoup 的答案中每次都创建它更有效。

【讨论】:

  • 但是我每次添加/删除新命令时都必须修改地图?我不能动态地这样做吗?
  • 是的,我后来也意识到了这一点,并在我看到您的评论之前对其进行了编辑:)
  • @Alfred。您可以尝试反射,但这并不是最佳选择。使用地图,将其保存在易于添加新命令的安全位置。
  • 您可以使用 Spring 之类的东西将实现注入到地图中,但您不会绕过这样一个事实,即您必须触摸一些代码来添加新的逻辑分支, 无论是 if() 块还是 map.add(new Command());或在 Spring XML 文件中。
  • 或者你可以使用我的答案和Java的SPI机制。
【解决方案2】:

一种方法可能是拥有一个接口ICommand,它是一个命令的通用合同,例如:

public interface ICommand {
    /** @param context The command's execution context */
    public void execute(final Object context);
    public String getKeyword();
}

然后您可以使用 Java 的 SPI 机制自动发现您的各种实现并将它们注册到 Map&lt;String,ICommand&gt; 中,然后执行 knownCommandsMap.get(input).execute(ctx) 或类似的操作。

这实际上使您能够将服务与命令实现分离,从而有效地使这些实现可插拔。

向 SPI 注册实现类是通过添加一个名为 ICommand 类的完全限定名称的文件来完成的(因此,如果它在包 dummy 中,则该文件将在您的类路径中为 META-INF/dummy.ICommand),然后您'将它们加载并注册为:

final ServiceLoader<ICommand> spi = ServiceLoader.load(ICommand.class);
for(final ICommand commandImpl : spi)
    knownCommandsMap.put(commandImpl.getKeyword(), commandImpl);

【讨论】:

  • 这仅在 JDK 6 中可用
  • 你能解释一下SPI部分吗?
  • @fuzzy lollipop:实际上它已经在 J​​DK 5 中,但在一个非公共包(sun.misc ...)中。一开始它应该是 JDK 5 的一部分。
  • @Alfred:添加了 SPI 使用示例,假设您在某处有一个初始化的地图等等......(我刚刚添加了 SPI 的相关代码)
  • javadocs 说“自 1.6 起” sun.* 中的任何内容都不应使用
【解决方案3】:

接口、工厂和一点反射怎么样?您仍然需要处理错误输入的异常,但您总是需要这样做。使用此方法,您只需为新输入添加一个新的 Executor 实现。

public class ExecutorFactory
{
    public static Executor buildExecutor(String input) throws Exception
    {
        Class<Executor> forName = (Class<Executor>) Class.forName(input);
        return (Executor) executorClass.newInstance();
    }
}

public interface Executor
{
    public void execute();
}


public class InputA implements Executor
{
    public void execute()
    {
        // do A stuff
    }
}

public class InputB implements Executor
{
    public void execute()
    {
        // do B stuff
    }
}

你的代码示例就变成了

String input;
ExecutorFactory.buildExecutor(input).execute();

【讨论】:

  • 这也是个好主意,它只需要每次创建一个新实例的成本。
  • 执行者的名字从何而来?如果是客户端/用户输入,恶意用户可以实例化系统上的任何类。如果类初始化改变了系统的状态,那么攻击者将获得对系统的某种程度的控制。然后在实例化和抛出异常之间会出现竞争条件(假设该类不是 Executor 的子类),在此期间攻击者可以利用其他类打开的任何功能......跨度>
  • @atk 是的。我假设存在一些未表示的错误处理和输入检查。我将添加一个检查,该命令至少是一个 Executor。
【解决方案4】:

在枚举类上构建命令模式可以减少一些样板代码。假设input.equals(x)中的x是“XX”,input.equals(y)中的y是“YY”

enum Commands {
   XX {
     public void execute() { doX(); }        
   },
   YY {
     public void execute() { doY(); }        
   };

   public abstract void execute();
}

String input = ...; // Get it from somewhere

try {
  Commands.valueOf(input).execute();
}
catch(IllegalArgumentException e) {
   unknown_command();
}

【讨论】:

  • 也是一个好主意,但这是非常紧密的耦合。你不能再提供来自“外部”的命令了。
【解决方案5】:

您说您正在处理来自套接字的输入。输入多少?它有多复杂?它的结构如何?

根据这些问题的答案,您最好编写一个语法并让解析器生成器(例如,ANTLR)生成输入处理代码。

【讨论】:

  • 只是一个简单的协议。例如内存缓存。
  • @Alfred - 在 memcached 的情况下,这是一个非常简单的协议,有六个操作并且不太可能改变,我会使用 if-else 构造。没有理由仅仅为了“面向对象”而使您的代码模式变得复杂。但是,我会创建一个对象来包装命令和/或响应,并处理所有解析。并且可能创建一个枚举来表示命令(这会将 if-else 链变成一个开关)。
  • 如果我希望能够轻松替换行为,我可以使用模板方法模式,为每个操作创建抽象方法,并让我的应用反射性地实例化子类。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-31
  • 1970-01-01
  • 2017-06-01
  • 2013-02-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多