4

我正在尝试为交互式 Java 程序做一个非常简单的命令行库。您启动 Java 程序,它会提示您输入命令。语法是:

> action [object_1, [object_2, [... object_n] ... ]]

例如:

> addUser name "John Doe" age 42

在这里action = "addUser", object_1 = "name", object_2 = "John Doe", object_3 = "age", object_4 = "42"

动作之后的一切都是对象即动作使用对象)。您可以看到动作和对象是简单的字符串。如有必要,对象字符串也可以转换为数字。

我的计划是,这个命令行库的用户将简单地创建方法(属于任何合适的 Java 对象)并将每个方法分配给特定的操作。命令行中的对象成为用户分配的方法的参数。为上述示例实现方法的合适类是:

class Foo {
    public void addUserHandler(
            String param1, String param2, String param3, Integer param4) {
        do.someThing();
    }
}

当用户键入命令时,程序员分配的相应函数会被调用,并使用命令行中指定的参数。

我知道 Java 没有函数指针(如 C)或委托(如 C#),实现回调的方法是通过接口,但是,我不知道在这种情况下如何使用接口。我看到的接口问题是它们有:

  1. 要实现的固定数量的功能
  2. 要实现的功能有一个固定的声明。

(1) 的问题是我的库的用户可能决定实现任意数量的功能,以实现他想要支持的任意数量的操作。(2) 的问题是用户希望分配具有描述性名称的函数,例如 addUserHandler() 用于“addUSer”操作。

如果 Java 有委托或函数指针,我只需在字符串(表示操作)和委托(用于程序中操作的实际实现)之间创建一个映射。我想我可以用反射来模拟委托,但它很血腥而且我失去了类型安全性,因为我必须对类和方法使用字符串名称。有没有更好的办法?

谢谢,

4

3 回答 3

6

如果您希望用户获得自动类型转换(例如从字符串到整数),那么反射是唯一的方法。如果您让用户完成工作,那么您不需要反射(并且您获得类型安全):

你的命令界面:

public interface MyCommand {
  public void execute(String[] args);
}

一个实现:

public class MyObj {
  public void doSomething(String param1, Integer param2) {
  }

  private void register() {
    mainApp.registerCommand("doSomething", new MyCommand() {
      @Override public void execute(String[] args) {
        doSomething(args[0], Integer.parseInt(args[1]));
      }});
  }
}
于 2009-10-06T15:26:10.793 回答
2

感谢您对这个问题的纯粹敬畏。你几乎已经达到了 Java 所能做的极限。(当然,像你描述的那样委派在任何函数式语言中都是轻而易举的事。)

首先,限制#2 应该不是问题。从 1.5 开始,Java 不再限制您在方法中使用固定声明。您可以使用省略号来表示可变数量的参数,如下所示

public static double average( double... numbers ) {
    double total = 0.0;
    for (double d : numbers) {
        total += d;
    }
    return total / numbers.length;
}

我不完全确定如何绕过限制 #1,但我正在考虑使用泛型和/或匿名内部类的东西。我在这里猜测——我自己没有尝试过这样做——但你可以创建一个函数名称和委托类的映射,如下所示:

public interface Callback {...}

public interface AddUserCallBack extends Callback {...}

public class UserImpl<T extends Callback> {
    public T getDelegateRoutine();
    ... 
}

Java 中的泛型有一些与之相关的令人毛骨悚然的挫败感,主要是由于类型擦除。您可能需要同时兼顾接口和抽象基类,然后才能让它做您想做的事。但是这样的事情应该适合你。

于 2009-10-06T15:17:17.097 回答
1

反射解决方案还不错。你可以这样做(语法可能不是 100%,Java 不是我最好的语言,我这里没有编译器):

interface Action {
    public void Apply(object[] args);
}

class AddUser implements Action {
    Type[] argTypes;
    public AddUser() {
        argTypes = {String, String, String, Integer};
    }
    public void Apply(object[] args) {
        // Now interpret args based on the contents of argTypes, and do your thing.
        // The map would look like "adduser" => new AddUser()
        // and could be invoked as map["adduser"].Apply(args)
    }
}

但正如 rtperson 所说,您正在遇到 Java 的一些基本限制。几乎任何其他现代语言都会更好地为您服务。

于 2009-10-06T15:20:24.167 回答