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)の問題は、ユーザーが「addUSer」アクションのaddUserHandler()のように、わかりやすい名前で関数を割り当てたいということです。

Javaにデリゲートまたは関数ポインターがある場合は、文字列(アクションを表す)とデリゲート(プログラムでのアクションの実際の実装用)の間にマップを作成するだけです。Reflectionを使用してデリゲートをエミュレートできると思いますが、クラスとメソッドに文字列名を使用する必要があるため、これは面倒で型の安全性が失われます。より良い方法はありますか?

ありがとう、

4

3 に答える 3

6

ユーザーにautomagicタイプの変換(たとえば、文字列からintへ)を取得させたい場合は、リフレクションが唯一の方法です。ユーザーに作業を行わせる場合は、リフレクションは必要ありません(そして型安全性が得られます)。

コマンドインターフェイス:

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 に答える