Карта делегатов на Яве - PullRequest
4 голосов
/ 06 октября 2009

Я пытаюсь создать очень простую библиотеку командной строки для интерактивных программ на 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".

Все, что после action является object (то есть действие использует объекты). Вы можете видеть, что действие и объект являются простыми строками. Строки объекта также могут быть преобразованы в числа при необходимости.

Мой план состоит в том, чтобы пользователь этой библиотеки командной строки просто создавал методы (принадлежащие любому подходящему объекту 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 были делегаты или указатели на функции, я бы просто создал карту между строками (представляющими действия) и делегатами (для фактической реализации действия в программе). Я думаю, что могу эмулировать делегатов с помощью Reflection, но это ужасно, и я теряю безопасность типов, поскольку мне пришлось бы использовать имена String для классов и методов. Есть ли лучший способ?

Спасибо

Ответы [ 3 ]

6 голосов
/ 06 октября 2009

Если вы хотите, чтобы пользователь получал автоматический перевод типов (например, из строк в целые), то отражение - единственный способ. если вы заставляете пользователя выполнять работу, вам не нужно размышлять (и вы получаете безопасность типов):

Ваш командный интерфейс:

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]));
      }});
  }
}
2 голосов
/ 06 октября 2009

Слава вам за удивительность этого вопроса. Вы в значительной степени находитесь за пределами возможностей 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, но я думаю о чем-то с generics и / или анонимными внутренними классами . Я предполагаю здесь - я не пытался сделать это сам - но вы могли бы создать карту имен функций и классов делегатов, например, так:

public interface Callback {...}

public interface AddUserCallBack extends Callback {...}

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

Дженерики в Java имеют некоторые неприятные разочарования, связанные, прежде всего, со стиранием типов. Возможно, вам придется жонглировать как интерфейсами, так и абстрактными базовыми классами, прежде чем вы заставите его делать то, что вы хотите. Но что-то вроде этого должно работать для вас.

1 голос
/ 06 октября 2009

Отражающее решение не так уж плохо. Вы можете сделать что-то вроде (синтаксис не может быть 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. Почти любой другой современный язык будет вам полезнее.

...