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

StackOverflow https://stackoverflow.com/questions/626669

  •  06-07-2019
  •  | 
  •  

Вопрос

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

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

  • запланированное / отложенное выполнение
  • асинхронное выполнение, включающее обмен сообщениями

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

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

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

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

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

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

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

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

Это было полезно?

Решение

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

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

   sender.addListener(new ListenerInterface()
   {
      public void callback(Object arg)
      {
         handlerFunction(arg);
      }
   });

Другие советы

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

Еще одна ссылка здесь

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top