Без указателей на функции, каков рекомендуемый способ реализации Callback в Java - кроме интерфейсов? - PullRequest
0 голосов
/ 09 марта 2009

У меня есть пара классов, которые хотят передать друг другу некоторую информацию и перезвонить позже, используя эту информацию (шаблон обратного вызова).

В моем приложении этот механизм служит двум целям:

  • Запланированное / отложенное выполнение
  • асинхронное выполнение с использованием обмена сообщениями

Мои объекты в основном говорят друг другу "когда вы закончите делать X, перезвоните мне и скажите мне сделать Y с Z (потому что к тому времени я забуду об этом)" . Где X может просто ждать нужного времени, но также общаться с удаленным сервисом или вызывать локальную функцию.

Теперь, если бы в Java были указатели функций (или эквивалентные), я бы реализовал некоторый класс "Job", содержащий один плюс необходимые ему аргументы. Например, в PHP эта структура должна хранить имя класса, имя функции и массив аргументов. В C это был бы указатель на функцию, и мне пришлось бы указывать одинаковые номера и типы аргументов для всех вызовов.

В Java обычный подход состоит в том, чтобы иметь интерфейс, который реализован всеми классами, которые хотят вызываться, например:

public interface ICallable {
    public void call_me(Object data);
}

Теперь это не будет работать для меня, потому что

  • объекты, которые будут вызваны, могут иметь другой набор методов для вызова
  • звонящий не тот, кто решает, какой вызов следует сделать

Может быть, моя проблема в том, что я пытаюсь создать общую структуру данных и вызывать процедуру для различных обратных вызовов, но в принципе мне кажется, что это имеет смысл.

Какой хороший шаблон проектирования подходит для этой ситуации в Java?

Ответы [ 8 ]

6 голосов
/ 09 марта 2009

Я думаю, вы найдете, что интерфейс является лучшим решением. Я не понимаю вашего возражения против них. Если получателю нужно вызвать другой метод, тогда вызовите метод Interface для вызова другого метода.

Если ваш класс приемника должен иметь несколько обработчиков, вы можете использовать анонимные внутренние классы, которые реализуют Интерфейс, по одному для каждого типа обработчика, который вам нужен. Например, если отправитель использует интерфейс с именем «ListenerInterface», ваш получатель может определить анонимный внутренний класс, который реализует ListenerInterface и вызывает метод получателя «handlerFunction».

   sender.addListener(new ListenerInterface()
   {
      public void callback(Object arg)
      {
         handlerFunction(arg);
      }
   });
3 голосов
/ 09 марта 2009

Возможно, вы используете концепцию, аналогичную анонимным внутренним классам

Другая ссылка здесь

2 голосов
/ 09 марта 2009

Если вы действительно не хотите использовать интерфейсы (и поверьте мне, что вы делаете!), Вы можете использовать рефлексию, чтобы получить метод, который вы хотите вызвать, а затем передать его тому, который должен выполнить вызов. Это так близко к указателю на функцию C, как вы собираетесь получить.

Отражение будет медленнее (вероятно), чем интерфейсный способ сделать это (но это не будет иметь значения, если его часто вызывать).

Отражение не будет иметь тип времени компиляции, проверяющий, как интерфейс использует этот способ (что означает, что вы можете потерпеть крах во время выполнения).

Отражение будет более уродливым кодом, чем интерфейсный способ сделать это.

Но если вы действительно действительно хотите смоделировать указатели на функции C, то рефлексия настолько близка, насколько вы собираетесь.

2 голосов
/ 09 марта 2009

Я бы использовал интерфейс, как вы описали, а затем использовал бы анонимный класс для ваших обратных вызовов. Например, изнутри вашего абонента:

ICallback callback = new ICallback() { 
    public void call_me(Object data) { this.actionToCall(data); }
}
serverObject.doX(callback);

(синтаксис может немного отличаться от этого.)

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

1 голос
/ 09 марта 2009

Обратные вызовы - это общая идея, и вы не должны пытаться использовать универсальный интерфейс. Сделайте ваш интерфейс обратного вызова специфичным для использования. Он должен соответствовать семантике типа, которому он передается и который выполняет обратный вызов. Обычная реализация интерфейса обратного вызова будет выполняться как анонимный внутренний класс. Не поддавайтесь искушению реализовать интерфейс во внешнем классе, что приведет к некогерентному типу.

0 голосов
/ 09 марта 2009

Я не слишком углубился в это, но интерфейс Java 5+ Future (или его подчиненные интерфейсы) может быть полезен для этого.

0 голосов
/ 09 марта 2009

Я уверен, что есть некоторая интересная модель, которая решает вашу проблему, но я бы выбрал простой подход с использованием инфраструктуры. Заполнение запросов и ответов в очереди сообщений, таких как JMS, может сделать вашу систему более простой и надежной, поскольку вы можете обрабатывать события в течение нескольких секунд или недель, не беспокоясь о перезапусках системы, постоянстве и целостности сообщений.

0 голосов
/ 09 марта 2009

Возможно, я неправильно понимаю проблему, но мне кажется, что вы выиграете от установки какой-либо системы событий.

По сути, вы устанавливаете обработчик событий, и классы, которые хотели бы знать о событии, подписываются на обработчик событий. Так что для вашей проблемы, когда X будет выполнен, он запустит FinishedProcessingEvent (), на который будет подписан ваш объект. Когда ваш объект получит это событие (вместе с любыми другими подписчиками, которые заботятся о событии), он выполнит правильное действие (Y с Z). Эти действия определяются внутри объектов, поэтому разные объекты могут выполнять разные действия на основе одних и тех же событий.

Помогает ли это?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...