2016-01-08 2 views
1

Я пытаюсь реализовать прослушиватель записи в Map Scala.Реализация приемника ввода карты

Идея:

  1. мне нужно подписаться на карте от службы.
  2. Мне нужно уведомить всех подписчиков Карты, когда добавляется/обновляется запись с определенным ключом.
  3. Мне нужно иметь доступ к Карте из других служб, чтобы проверить значение записи.

Я не смог найти готовое решение для этого, поэтому я пытался реализовать его с Акки:

class TrackingService(system: ActorSystem) extends LazyLogging { 
    private val trackingActor = system.actorOf(TrackingActor.props) 
    private val duration = Duration(15, TimeUnit.SECONDS) 
    private implicit val timeout = Timeout(duration) 

    def fireEvent(key: String): Unit = { 
    TrackingActor ! EventFired(key) 
    } 

    def eventHappened(key: String): Future[Boolean] = { 
    TrackingActor ? DoesEventHappened(key)).mapTo[Boolean] 
    } 

    def registerHiddenCardListener(key: String, data: MyData): Unit = { 
    TrackingActor ! Subscribe(key, data) 
    } 
} 

case class EventFired(key: String) 
case class EventHappened(key: String) 
case class EventHappenedResponse(happened: Boolean) 

case class Subscribe(key: String, data: Data) 
class TrackingActor extends Actor with LazyLogging { 
    var eventMap: Map[String, Boolean] = Map.empty[String, Boolean] 
    var dataMap: Map[String, List[Data]] = Map.empty[String, List[Data]] 

    def receive: Receive = { 
    case Subscribe(key, data)  => 
     val currentData: List[Data] = dataMap.getOrElse(key, Nil) 
     val newData = data :: currentData 
     dataMap = dataMap + (key -> newData) 
    case EventHappened(key)   => sender() ! EventHappenedResponse(eventMap.getOrElse(key, false)) 
    case [email protected](key)   => 
     eventMap = eventMap + (key -> true) 

     for { 
     dataOpt <- dataMap.get(key) 
     data <- dataOpt 
     } { 
     // do callback with data (e.g. send email) 
     } 
    case x => logger.warn(s"Received unknown message: $x") 
    } 
} 

object TrackingActor { 
    def props: Props = Props(classOf[TrackingActor]) 
} 

Что мне не нравится в этом решении: Я не люблю спрашивать шаблон, но мне нужно иметь доступ к записям из неактивных классов. Кроме того, мне не нравится иметь 2 карты, но мне нужно хранить где-то данные, которые следует использовать для обратного вызова.

Любые идеи о том, как я могу улучшить это?

ответ

1

Вот идея:

case class Subscribe[A, B](f: (A, B, NotifyingMap[A,B]) => Any) 

case class Event[A, B](key: A, value: B, originator: NotifyingMap[A,B]) 

case class RegisterObserver(actorRef: ActorRef) 

/** 
    * Subscribes to events 
    */ 
class Subscriber[A,B]{ 

    def register(actorSystem: ActorSystem) = { 
    val actor = actorSystem.actorOf(Props(classOf[Observer[A,B]])) 
    actor ! Subscribe(handleEvent) 
    } 

    def handleEvent(key: A, value: B, notifyingMap: NotifyingMap[A, B]) = { 
    println(s"Saw key $key with value $value") 
    } 
} 

/** 
    * Observer of events that will call a partial function when 
    * an event comes in. 
    */ 
class Observer[A, B] extends Actor{ 
    var f: (A,B,NotifyingMap[A,B]) => Any = _ 

    def receive = { 
    case x: Subscribe[A, B] => 
     f = x.f 
     Notifier() ! RegisterObserver(self) 
    case e: Event[A,B] => 
     f(e.key, e.value, e.originator) 
    } 
} 

/** 
    * Notifier that sends out the event to all registered observers. 
    */ 
class Notifier extends Actor { 
    var observers = List[ActorRef]() 

    def receive = { 
    case x: RegisterObserver => 
     observers = x.actorRef :: observers 
    case x: Event[_,_] => 
     observers.foreach(_ ! Event) 
    } 
} 

/** 
    * Singleton notifier. 
    */ 
object Notifier{ 

    var notifier: ActorRef = _ 

    def create(actorSystem: ActorSystem) = 
    actorSystem.actorOf(Props(classOf[Notifier])) 

    def apply(): ActorRef = notifier 
} 

/** 
    * Class that sends out an event when an item is put. Also allows for 
    * getting an item based on a key. 
    */ 
class NotifyingMap[A, B](){ 
    val map: TrieMap[A,B] = TrieMap[A,B]() 

    // Specific business logic here on when you publish the event. 
    def put(key: A, value: B) = { 
    map.put(key, value).foreach(v => Notifier() ! Event(key, v, this)) 
    } 

    def get(key: A) = map.get(key) 
} 

Делая это, вы можете держать ваш подписчик в не- Actor класс в то же время позволяя ей реагировать на события. Вы также можете вызвать простые старые методы на своем NotifyingMap, так как это всего лишь класс, а не Actor.

Мне лично нравится хранить информацию о обратном вызове в сообщениях. Обычно вы видите это, имея еще один ActorRef в классе case. В этом примере мы имеем NotifyingMap в классе case, поэтому мы знаем, откуда произошло событие, и может соответствующим образом вызвать метод get.

Полное раскрытие информации: я не использовал ни один из этих кодов. Он компилируется.

Смежные вопросы