Как использовать шаблонный узор с актерами Akka?
Я пытаюсь реализовать признак Pub/Sub для смешивания с другими аккскими актерами с использованием стекируемого признака.
Вот что я придумал:
trait PubSubActor extends Actor {
abstract override def receive =
super.receive orElse {
case Subscribe(topic) => /* ... */
case Publish(topic, msg) => /* ... */
}
}
class MyActor extends Actor with PubSubActor {
override def receive = {
case SomeMessage(a, b, c) => /* ... */
}
}
В этот момент компилятор возвращает ошибку:
ошибка: переопределяющий метод получения в признаке метода MyActor... get требует модификаторов абстрактного переопределения.
Можете ли вы объяснить мне, почему это не работает? Как я могу исправить это, чтобы он работал?
Спасибо!
UPDATE
Следующие работы:
trait PubSubActor extends Actor {
abstract override def receive =
super.receive orElse {
case Subscribe(topic) => /* ... */
case Publish(topic, msg) => /* ... */
}
}
class MyActor extends Actor {
override def receive = {
case SomeMessage(a, b, c) => /* ... */
}
}
class MyActorImpl extends MyActor with PubSubActor
Но почему? Почему я могу получить такое поведение, которое хочу, но не другое? Любые причины? Я не могу понять, что разница между этими двумя образцами делает разницу.
Ответы
Ответ 1
Там простое и краткое решение:
Определите признак приема, который связывает несколько функций приема с помощью orElse
:
trait Receiving {
var receivers: Receive = Actor.emptyBehavior
def receiver(next: Actor.Receive) { receivers = receivers orElse next }
def receive = receivers // Actor.receive definition
}
Использование этого в актерах легко:
trait PubSubActor extends Receiving {
receiver {
case Publish => /* I'm the first to handle messages */
}
}
class MyActor extends PubSubActor with Receiving {
receiver {
case SomeMessage => /* PubSubActor didn't handle, I receive the message */
}
}
Будет вызван первый прием PubSubActor. Если сообщение не было обработано, оно будет передано в приемник MyActor.
Ответ 2
Вы, безусловно, можете добиться того, что ищете, используя функцию Akka composable actor. Это описано немного в Расширение участников с использованием цепочки PartialFunction.
Сначала, код инфраструктуры (прямо из документов):
class PartialFunctionBuilder[A, B] {
import scala.collection.immutable.Vector
// Abbreviate to make code fit
type PF = PartialFunction[A, B]
private var pfsOption: Option[Vector[PF]] = Some(Vector.empty)
private def mapPfs[C](f: Vector[PF] => (Option[Vector[PF]], C)): C = {
pfsOption.fold(throw new IllegalStateException("Already built"))(f) match {
case (newPfsOption, result) => {
pfsOption = newPfsOption
result
}
}
}
def +=(pf: PF): Unit =
mapPfs { case pfs => (Some(pfs :+ pf), ()) }
def result(): PF =
mapPfs { case pfs => (None, pfs.foldLeft[PF](Map.empty) { _ orElse _ }) }
}
trait ComposableActor extends Actor {
protected lazy val receiveBuilder = new PartialFunctionBuilder[Any, Unit]
final def receive = receiveBuilder.result()
}
Затем поведение, которое вы хотите создать для актеров:
trait PubSubActor { self:ComposableActor =>
receiveBuilder += {
case Subscribe(topic) => /* ... */
case Publish(topic, msg) => /* ... */
}
}
trait MyActor { self:ComposableActor =>
receiveBuilder += {
case SomeMessage(a, b, c) => /* ... */
}
}
И, наконец, фактический актер, который использует эти композиции:
class MyActorImpl extends ComposableActor with PubSubActor with MyActor
Ответ 3
Попробуйте наоборот:
object Subscription {
case object Subscribe
case object Unsubscribe
}
trait Subscription {
this: Actor =>
import Subscription._
var subscribers = Set.empty[ActorRef]
def receive: Receive = {
case Subscribe => subscribers += sender
case Unsubscribe => subscribers -= sender
}
}
class MyActor extends Actor with Subscription {
def receive = super.receive orElse {
case msg => // handle msg
}
}
Обратите внимание, что это все еще использует шаблон стекируемых признаков, который скрыт из-за того, что я опустил ядро. Так что-то вроде этого все равно будет работать (по крайней мере, я думаю, что буду, ATM у меня нет времени, чтобы проверить, компилируется ли он).
class Core extends Actor {
def receive = Actor.emptyBehavior
}
class MyActor extends Core with Subscription
Кстати, вы можете больше узнать о шаблоне (не относящемся к Актерам) здесь.
Ответ 4
Во-первых, извините меня за мой английский
Я думаю, дело в том, что абстрактный алгоритм переопределения требует наличия конкретной реализации метода приема, но в первой конструкции
class MyActor extends Actor with PubSubActor {
override def receive = {
case SomeMessage(a, b, c) => /* ... */
}}
это не сделано
Причина в том, что компилятор scala делает линеаризацию для наследования
поэтому в цепочке методов приема мы имеем следующую последовательность:
1) override def receive = {
case SomeMessage(a, b, c) => /* ... */
}
2) abstract override def receive = super.receive orElse {
case Subscribe(topic) => /* ... */
case Publish(topic, msg) => /* ... */
}
3) then Actor.receive - it hasn't an implementation
Так что PubSubActor.receive нельзя вызвать, потому что он использует super.receive,
в свою очередь super.receive полагается на Actor.receive, но Actor.receive не имеет реализации.
Во второй конструкции
class MyActor extends Actor {
override def receive = {
case SomeMessage(a, b, c) => /* ... */
}}
class MyActorImpl extends MyActor with PubSubActor
мы получаем цепочку методов
1)
abstract override def receive = super.receive orElse {
case Subscribe(topic) => /* ... */
case Publish(topic, msg) => /* ... */
}
2)
override def receive = {
case SomeMessage(a, b, c) => /* ... */
}
3), то Actor.receive - у него нет реализации
Итак, PubSubActor.receive может успешно вызвать super.receive
Дополнительная информация:
Штабелируемые черты
Scala спецификация языка, см. 5.1.2