2014-09-18 10 views
6

Aby zaimplementować własnego niestandardowego aktora w Akka (powiązanie Java), rozszerz klasę podstawową UntypedActor. Wymaga to, aby zdefiniować własne onReceive(...) metody:Akka/Java: Obsługa wielu typów wiadomości wewnątrz niestandardowego aktora?

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

Problem w zasięgu ręki jest ustalenie strategii postępowania wiadomość, która umożliwia podmiotom obsługiwać Multiple rodzaje komunikatów. Jedną ze strategii byłoby użycie refleksji/typów. Problem polega na tym, że:

  1. Zmusza nas do tworzenia pustych "klas powłoki", które nie robią nic więcej niż nadają znaczenie semantyczne dla wiadomości (patrz poniżej); i
  2. to świnie parametr message i pozwala nam jest w stanie przejść nic dynamiczny lub sensowne

Przykład pustej klasy powłoki:

public class EmptyShellMessage { } 

Następnie w sposób onReceive wyglądałby następująco:

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

Więc nie tylko tworzymy klasę skądinąd bezużyteczne, ale ponieważ Object message jest obecnie używany do przekazywania informacji o klasie/typie wiadomości, nie możemy jej użyć do przechowywania jakichkolwiek informacji, szczególnie informacji dynamicznych/uruchomieniowych, które inny aktor mógłby chcieć przekazać.

Czasem widzę odmianę tego:

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

ale tutaj używamy instanceof która jest uważana przez wielubyć ogromny antywzorzec projektowy (tylko google "instanceof antywzorzec projektowy ").

wtedy mamy teksty stałe:

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

Teraz onReceive wygląda następująco:

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

Problem polega na tym, że możemy mieć duży system aktor z setek lub nawet tysięcy różnych zdarzeń/wiadomości typy do obsługi. To wyliczenie staje się duże i trudne do utrzymania. Ma również ten sam problem, co strategia odbicia, powyżej której uniemożliwia nam wysyłanie dynamicznych informacji między aktorami.

Ostatnią rzeczą mogę myśleć jest użycie ciągi:

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

Ale nienawidzę tego. Kropka. Koniec zdania.

Więc pytam: czy brakuje tu czegoś oczywistego, może innej strategii? W jaki sposób aplikacje Java/Akka powinny obsługiwać dużą liczbę typów zdarzeń/wiadomości i określić, które z nich obsługują w metodzie onReceive?

Odpowiedz

6

Nie brakuje niczego. Nie jestem też fanem.W Scali jest nieco lepiej, ponieważ metoda onReceive może zostać zamieniona, aby symulować zmieniający się stan protokołu i wykorzystuje częściowe funkcje, które są trochę przyjemniejsze niż w przypadku/elseif/else ... ale nadal jest nieprzyjemnie. W Erlang jest fajniej, skąd wziął się ten model, ale biorąc pod uwagę ograniczenia, z jakimi boryka się zespół akka, dokonali właściwych wyborów projektowych i wykonali świetną robotę.

Alternatywną strategią jest wykonanie podwójnej wysyłki. Tak więc przekazuj komendę do aktora, aby działał, lub odszukaj w mapie obsługę wiadomości. Agenci Akka są w zasadzie pierwszymi, a kiedy są przyzwyczajeni do ich siły, są całkiem mili. Ogólnie jednak podwójna wysyłka zwiększa złożoność, dlatego w większości przypadków trzeba się przyzwyczaić do standardowego podejścia. Które w języku Java oznacza albo instanceof, albo instrukcję switch.


Przykład podwójnej wysyłki (kod psuedo), załączony tutaj dla kompletności, aby pomóc w zrozumieniu. Jako podejście przychodzi z ostrzeżeniami zdrowotnymi, więc powtarzam; standardowe podejście jest standardowe z jakiegoś powodu i że należy go używać w 99% przypadków.

onReceive(msg) { 
    msg.doWork() 
} 

Problem z tym podejściem polega na tym, że teraz komunikat musi wiedzieć, jak sam się przetwarzać, co jest zanieczyszczone; powoduje silne sprzężenie i może być kruchy. Fuj.

więc możemy pójść z użyciem odnośnika do ładowarki (polecenia wzór stylie)

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

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

    h.doWork(msg) 
} 

tu problemem jest to, że teraz nie jest jasne, jakie są polecenia i następujący kod poprzez polega skakali więcej. Ale są chwile, że warto to zrobić.

Jak zauważył Roland, należy zachować ostrożność przy przekazywaniu odniesienia do samego aktora. Powyższe przykłady nie podważają tego problemu, ale byłoby to łatwe do pokusy.

+0

Ahh, dzięki @Chris K (+1) - miło wiedzieć, że nie jestem tylko ten, kto tak czuje, jest niezręczny. Nie jestem do końca obeznany z podwójną wysyłką, ale z mojego wyszukiwania google wynika, że ​​po prostu napisałbym jeden przeciążenie onReceive dla każdego typu wiadomości, którą mój aktor musi obsługiwać. Więc jeśli chcę, aby mój aktor obsługiwał wiadomości "Fizz" i "Buzza", będę miał 2 metody "onReceive": (1) 'public void onReceive (Fizz fizz)' and (2) 'public void onReceive (Buzz buzz) '.Czy to jest poprawne? Dzięki jeszcze raz! – smeeb

+2

@smeeb Potrzebna jest również abstrakcyjna metoda na wiadomości, która akceptuje aktora. W każdej konkretnej implementacji wiadomości, ta metoda przekazuje wiadomość do aktora. Chciałem to zasugerować, ale nie podoba mi się zależność od czasu kompilacji, którą tworzy z wiadomości do aktora. Czy nie chciałbyś, aby twoje wiadomości były nieświadome aktora? Sądzę, że można tego uniknąć, deklarując interfejs protokołu dla twojego aktora do implementacji (wszystkie przeciążone metody onReceive), a wtedy twoje wiadomości mogą na tym polegać. – erickson

+0

@erickson dobrze powiedział. –

0

Zdecydowanie zalecam, aby nie omijać odniesienia Actor'a o this, ponieważ to z łatwością zachęca do przekazania go przez granice kontekstu wykonawczego, co może się zdarzyć w całkowicie niepozorny sposób. Drugi problem polega na tym, że wymaga on typu wiadomości, aby wiedzieć, w jaki sposób aktor chce sobie z nim poradzić, co jest dokładnym przeciwieństwem tego, jak przekazywanie wiadomości powinno oddzielić różne podmioty: aktor musi wybrać sposób radzenia sobie z wiadomością, a nie w inny sposób. na około.

Podmioty są jednostkami dynamicznymi, mogą odbierać dane wejściowe w nieprzewidywalnej kolejności (która stanowi rdzeń modelu), a zatem musimy użyć dynamicznej funkcji językowej do ich implementacji. instanceof jest częścią języka, aby ułatwić wykrywanie typów wiadomości w czasie wykonywania. Niezależnie od tego, czy jest ona wykorzystywana w innych kontekstach, i niezależnie od tego, kto nazywa ją wzorcem, jest to właściwe narzędzie do tej pracy. Implementacje aktorów w innych językach - w tym czcigodny wzorzec używania Erlanga do uzyskania tego samego efektu i dopasowywanie wzorców to dokładnie to samo, co testy instanceof (plus wygodniejsze wyodrębnianie parametrów).

0

Został utworzony prosty wzór pasujący DSL dla Javy 8, które mogą być przydatne dla Akka aktorów: 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) 
    ); 
    } 
} 
Powiązane problemy