2014-09-18 3 views
6

Чтобы реализовать собственный пользовательский актер в Akka (привязка Java), вы расширяете базовый класс UntypedActor. Для этого необходимо определить свой собственный onReceive(...) метод:Akka/Java: Обработка нескольких типов сообщений внутри пользовательского актера?

@Override 
public void onReceive(Object message) { 
    // TODO 
} 

Проблема в стороны, определяющей стратегию обработки сообщений, которая позволяет актерам обрабатывать множественные типы сообщений. Одна из стратегий заключалась бы в использовании отражений/типов. Проблема в том, что:

  1. Это заставляет нас создавать пустые «классы оболочек», которые не более чем дают семантический смысл для сообщения (см. Ниже); и
  2. Это свиньям параметр message и мешает нам быть в состоянии передать что-нибудь динамический или смысл

Пример пустого класса оболочки:

public class EmptyShellMessage { } 

Затем в методе onReceive будет выглядеть так:

@Override 
public void onReceive(Class<?> message) { 
    if(message.isAssignableFrom(EmptyShellMessage.class)) { 
     // TODO 
    } else { 
     // TODO 
    } 
} 

Так что не только мы создаем иначе бесполезный класс, но так как Object message теперь используется для кодирования того, что представляет собой класс/тип сообщения, мы не можем его использовать, чтобы содержать больше информации, особенно информацию о динамике/времени выполнения, которую может передать другой актер.

Иногда я вижу изменение этого:

@Override 
public void onReceive(Object message) { 
    if(message instanceof FizzEvent) { 
     // TODO 
    } else { 
     // TODO 
    } 
} 

Но здесь мы используем instanceof, который считается по многих быть огромный антипаттерн (только Google "InstanceOf антипаттерн «).

Тогда мы имеем перечислений:

public enum ActorMessage { 
    FizzEvent, 
    BuzzEvent, 
    FooEvent, 
    BarEvent 
} 

Теперь onReceive выглядит следующим образом:

@Override 
public void onReceive(ActorMessage message) { 
    if(message.equals(ActorMessage.FizzEvent)) { 
     // TODO 
    } else { 
     // TODO 
    } 
} 

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

Последнее, что я могу думать о том, чтобы использовать строки:

@Override 
public void onReceive(String message) { 
    if(message.equals("FizzEvent")) { 
     // TODO 
    } else { 
     // TODO 
    } 
} 

Но я ненавижу это. Период. Конец предложения.

Так что я спрашиваю: я пропустил что-то очевидное здесь, возможно, другая стратегия? Как приложения Java/Akka должны обрабатывать большое количество типов событий/сообщений и указать, какой из них они обрабатывают в методе onReceive?

ответ

6

Нет, у вас ничего не пропало. Я тоже не поклонник.В Scala это немного лучше, потому что метод onReceive можно поменять местами, чтобы имитировать изменяющееся состояние протокола, и он использует частичные функции, которые немного приятнее, чем if/elseif/else ... но это все еще нехорошо. В Erlang это лучше, и именно здесь эта модель возникла, но из-за ограничений, с которыми столкнулась команда akka, они сделали правильный выбор дизайна и сделали отличную работу.

Альтернативная стратегия - выполнить двойную отправку. Итак, передайте команду актеру в действие или найдите в карте обработчик для сообщения. Акковые агенты по сути являются первыми, и когда они привыкли к их силе, они довольно приятные. Но в целом двойная отправка просто добавляет сложности, поэтому для большинства случаев просто нужно привыкнуть к стандартному подходу. Что в java означает либо if instanceof, либо оператор switch.

Пример двойной отправки (код psuedo), включенный здесь для полноты, чтобы помочь понять. В качестве подхода он касается предупреждений о вреде для здоровья, поэтому я повторяю; стандартный подход является стандартным по причине и что следует использовать это 99% времени.

onReceive(msg) { 
    msg.doWork() 
} 

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

Таким образом, мы можем пойти с помощью поиска для обработчиков (команда stylie)

val handlers = Map("msgid1"->Handler1, "msgid2->Handler2) 

onReceive(msg) { 
    val h = handlers(msg.id) 

    h.doWork(msg) 
} 

проблема здесь заключается в том, что в настоящее время остается неясным, какие команды и после кода через включают в себя прыгать больше. Но есть моменты, когда это стоит того.

Как указал Роланд, следует соблюдать осторожность при обходе ссылки на самого актера. Приведенные выше примеры не оскверняют эту проблему, но это будет легкий соблазн.

+0

Ааа, спасибо @ Крис K (+1) - приятно знать, что я не только тот, кто чувствует себя так, неловко. Я не совсем знаком с двойной отправкой, но из моего googling кажется, что я просто напишу одну перегрузку 'onReceive' для каждого типа сообщений, с которым должен справиться мой актер. Поэтому, если я хочу, чтобы мой актер обрабатывал сообщения «Fizz» и «Buzz», у меня было бы 2 метода onReceive: (1) 'public void onReceive (Fizz fizz)' и (2) 'public void onReceive (Buzz buzz) '.Это верно? Еще раз спасибо! – smeeb

+2

@smeeb Вам также нужен абстрактный метод для вашего сообщения, которое принимает вашего актера. В каждой конкретной реализации сообщения этот метод передает сообщение актеру. Я собирался предложить это, но мне не нравится зависимость времени компиляции, созданная из сообщений для актера. Разве вам не хотелось, чтобы ваши сообщения не знали о актере? Я думаю, этого можно избежать, объявив интерфейс протокола для вашего актора для реализации (все перегруженные методы onReceive), и тогда ваши сообщения могут зависеть от этого. – erickson

+0

@erickson хорошо сказано. –

0

Я настоятельно рекомендую отказаться от ссылки Актера this, потому что это легко приглашает вас передать его через границы контекста выполнения, что может происходить совершенно незаметно. Другая проблема заключается в том, что он требует, чтобы тип сообщения знал, как актер хочет его обработать, что является полной противоположностью тому, как передача сообщений должна развязывать разные сущности: актер должен выбрать, как обращаться с сообщением, а не наоборот вокруг.

Актеры являются динамическими объектами, они могут принимать входные данные в непредвиденной последовательности (которая лежит в основе модели), и поэтому мы должны использовать динамическую языковую функцию для их реализации. instanceof является частью языка для облегчения обнаружения типов сообщений. Независимо от того, злоупотребляют ли они в других контекстах, и независимо от того, кто называет его анти-шаблоном, это точно правильный инструмент для этой работы. Реализации актера на других языках, в том числе почтенный пример использования Erlang, для достижения такого же эффекта, и сопоставление шаблонов - это то же самое, что и тесты instanceof (плюс более удобное извлечение параметров).

0

Я создал простой шаблон, соответствующий DSL для Java 8, который может быть полезен для Akka актеров: https://github.com/strangepleasures/matchmetender

public class MatchingActor extends UntypedActor { 
    private LoggingAdapter log = Logging.getLogger(getContext().system(), this); 

    public void onReceive(Object message) throws Exception { 
    match(message, 
     (Foo foo) -> log.info("received a Foo: " + foo), 
     (Bar bar) -> log.info("received a Bar: " + bar), 
     (Baz baz) -> log.info("received a Baz: " + baz), 
     (unknown) -> unhandled(unknown) 
    ); 
    } 
} 
Смежные вопросы