Как мне лучше всего разделить поведение между актерами Akka?

У меня есть два актора Akka, которые реагируют на одни сообщения одинаково, а на другие — по-разному. Они оба отвечают на один и тот же набор сообщений. Хотите знать, как спроектировать двух моих актеров с их методами приема, через наследование, самообладание и т. д.? Я попытался объединить частичные функции из других трейтов с помощью orElse, что, к сожалению, предоставляет классу функциональность его трейта, плюс я не был уверен, как получение трейта может легко получить доступ к контексту актера. Встраиваемое модульное решение было бы идеальным, но мне интересно, решена ли где-то эта проблема?


person sdanzig    schedule 29.06.2013    source источник
comment
Два наиболее релевантных сообщения, которые я нашел, включают этот, показывающий цепочку частичных функций, по крайней мере, для акторов Scala: kotancode.com/2011/07/19/ ...и затем этот, показывающий шаблон, позволяющий стекировать получение путем вызова оболочка: grokbase.com/t/gg/akka-user /12ckfs8r7g/stackable-actor-traits ... Я думаю, что у них обоих есть свои сильные стороны, но я не знаю, как они получат доступ к контексту.   -  person sdanzig    schedule 29.06.2013


Ответы (2)


Есть действительно куча способов, которыми вы можете это сделать. Я перечислю два способа OO (аналогично тому, что предлагает @Randal Schulz) и еще один функциональный способ. Для первого возможного решения вы можете сделать что-то простое:

case class MessageA(s:String)
case class MessageB(i:Int)
case class MessageC(d:Double)

trait MyActor extends Actor{

  def receive = {
    case a:MessageA =>
      handleMessageA(a)

    case b:MessageB =>
      handleMessageB(b)

    case c:MessageC =>
      handleMessageC(c)
  }

  def handleMessageA(a:MessageA)

  def handleMessageB(b:MessageB) = {
    //do handling here
  }

  def handleMessageC(c:MessageC)
}

class MyActor1 extends MyActor{
  def handleMessageA(a:MessageA) = {}
  def handleMessageC(c:MessageC) = {}
}

class MyActor2 extends MyActor{
  def handleMessageA(a:MessageA) = {}
  def handleMessageC(c:MessageC) = {}
}

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

Теперь вариант этого подхода с использованием шаблона стратегии:

trait MessageHandlingStrategy{
  def handleMessageA(a:MessageA)

  def handleMessageB(b:MessageB) = {
    //do handling here
  }

  def handleMessageC(c:MessageC)
}

class Strategy1 extends MessageHandlingStrategy{
  def handleMessageA(a:MessageA) = {}
  def handleMessageC(c:MessageC) = {}  
}

class Strategy2 extends MessageHandlingStrategy{
  def handleMessageA(a:MessageA) = {}
  def handleMessageC(c:MessageC) = {}  
}

class MyActor(strategy:MessageHandlingStrategy) extends Actor{

  def receive = {
    case a:MessageA => 
      strategy.handleMessageA(a)

    case b:MessageB =>
      strategy.handleMessageB(b)

    case c:MessageC =>
      strategy.handleMessageC(c)
  }
}

Здесь подход заключается в передаче класса стратегии во время построения, который определяет обработку для a и c, при этом b снова обрабатывается одинаково независимо. Эти два подхода очень похожи и достигают одной и той же цели. Последний подход использует частичную цепочку функций и может выглядеть так:

trait MessageAHandling{
  self: Actor =>
  def handleA1:Receive = {
    case a:MessageA => //handle way 1
  }
  def handleA2:Receive = {
    case a:MessageA => //handle way 2
  }  
}

trait MessageBHandling{
  self: Actor =>
  def handleB:Receive = {
    case b:MessageB => //handle b
  }  
}

trait MessageCHandling{
  self: Actor =>
  def handleC1:Receive = {
    case c:MessageC => //handle way 1
  }
  def handleC2:Receive = {
    case c:MessageC => //handle way 2
  }  
}

class MyActor1 extends Actor with MessageAHandling with MessageBHandling with MessageCHandling{
  def receive = handleA1 orElse handleB orElse handleC1
}

class MyActor2 extends Actor with MessageAHandling with MessageBHandling with MessageCHandling{
  def receive = handleA2 orElse handleB orElse handleC2
}

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

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

person cmbaxter    schedule 29.06.2013
comment
Спасибо! Но я не уверен, что это решает одну из двух проблем, с которыми я боролся. Третий способ orElse, по-видимому, страдает той же проблемой, что и в примере с актером Scala ... тесно связывает класс с методом дескриптора внутри признака. Другие 2 похожи на пример с receiveWrapper, где функция receive вызывает что-то, что вы можете переопределить. Схема наследования/стратегии будет работать. Доступ к контексту исправлен с помощью параметра ActorContext для метода-оболочки. Я понял, что в моем случае единственными сообщениями, обрабатываемыми точно так же, являются неизвестные, так что это, вероятно, не стоит для меня. - person sdanzig; 30.06.2013
comment
Я не знаю, устал ли я просто, но да, это определенно не большая загадка, с решениями, которые вы предложили с параметром ActorContext. Тем не менее, я не жалею, что спросил, потому что уверен, что это поможет другим :) - person sdanzig; 30.06.2013

До сих пор у меня не было причин сожалеть о том, что я переместил столько фактической функциональности моих сервисов (так называемая «бизнес-логика») в более низкий уровень, «обычную» и синхронную (а иногда и блокирующую) библиотеку, которая может быть протестирована. без усложнения актеров. Единственное, что я помещаю в классы Актеров, — это общее, долгосрочное изменяемое состояние, на которое действует этот обычный библиотечный код. Это, конечно же, а также логика декодирования и отправки сообщений функции Akka Actor receive.

Если вы сделаете это, обмен логикой в ​​том направлении, в котором вы ищете, будет тривиальным.

person Randall Schulz    schedule 29.06.2013