觀察者模式之發佈訂閱

觀察者模式重點在於 觀察者和被觀察者的對應關係,以及將被觀察者的改變及時通知到相對應的觀察者。
這樣的模式基本上可以解決少量數據源的情景,在觀察者和被觀察者可能是多對多關係的情況下,強耦合的結構會讓代碼不夠清晰,難以維護。

在《JavaScript設計模式》一書中,提到了Observer和Publish/Subscribe的區別。

Observer模式要求希望接收到主題同志的觀察者(或對象)必須訂閱內容改變的事件。
Publish/Subscribe模式使用了一個主題/事件通道,這個通道介於希望接收到通知(訂閱者)的對象和激活事件的對象(發佈者)之間。該事件系統允許代碼定義應用程序的特定事件,這些事件可以傳遞自定義參數,自定義參數包含訂閱者所需的值。其目的是避免訂閱者和發佈者之間產生依賴關係。

這裏的關鍵點在於,通過一個事件中心,將發佈者和訂閱者的耦合關係解開,發佈者和訂閱者通過事件中心來產生聯繫。
打個比方,發佈者像是發佈小廣告的,事件中心是一個調度站,訂閱者則告訴事件中心,我關注A、B類型的廣告,如果有更新,請通知我。調度站記錄A,B類型下的訂閱者,等到A,B廣告發布時,通知到訂閱者。
這個例子裏,發佈者不關心訂閱者是誰,也不維護訂閱者列表,同訂閱者解耦,只將自己發佈的內容提交到事件中心。而訂閱者和主題的關係,交給了事件中心來維護。
畫一個類圖來解釋一下他們的關係。
發佈訂閱模式類圖

現在用scala實現一個例子

1. 首先定義發佈者接口

trait JobPublisher[E <: Event] {
    val jobPublisherName: String

    def publish(event: E): Unit
}

2. 定義訂閱者接口



trait JobSubscriber[E <: Event] {
    val jobSubscriberName: String

    def subscribe(topic: String)

    def update(event: E): Unit
}

3. 實現發佈者

class JobService extends JobPublisher[Event] {

 override val jobPublisherName: String = Const.JOB_PUBLISHER_TOPIC
    jobTopicChannel.addPublisher(this)

    override def publish(event: Event): Unit = {
        jobTopicChannel.publish(jobPublisherName, event)
    }

}

4. 實現訂閱者

class ApplyService extends JobSubscriber[Event] {
  this.subscribe(Const.JOB_PUBLISHER_TOPIC)

  override val jobSubscriberName: String = "test1"

  override def subscribe(topic: String): Unit = {
        jobTopicChannel.subscribe(topic, this)
    }

    override def update(event: Event): Unit = {
        event match {
            case taskEvent: TaskTriggerEvent => {
               
            }
            case _ => Unit
        }
    }

}

5. 輔助類

定義事件類

sealed abstract class Event

case class TaskTriggerEvent(task: Task) extends Event

定義TopicChannel, 維護 Publisher 和 SubScriber的關係

import javax.inject.{Inject, Singleton}


import modules.utils.{JobPublisher, JobSubscriber}
import play.api.Configuration

import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.ExecutionContext

@Singleton
class JobTopicChannel @Inject()(val conf: Configuration)(implicit executionContext: ExecutionContext) {
   
    private val publisherMap = mutable.HashMap[String, JobPublisher[Event]]()
    private val subscriberMap = mutable.HashMap[String, ArrayBuffer[JobSubscriber[Event]]]()

    def addPublisher(publisher: JobPublisher[Event]) = {
        this.publisherMap += (publisher.jobPublisherName -> publisher)
    }

    def removePublisher(publisher: JobPublisher[Event]) = {
        this.publisherMap -= publisher.jobPublisherName
    }

    def clearPublisher() = {
        this.publisherMap.clear
    }

    def subscribe(pub: String, subscriber: JobSubscriber[Event]) = {
        if (this.subscriberMap.contains(pub)) {
            this.subscriberMap(pub) += subscriber
        } else {
            this.subscriberMap += (pub -> ArrayBuffer(subscriber))
        }
    }

    def publish(pub: String, event: Event) = {
        if (this.publisherMap.contains(pub)) {
            this.subscriberMap(pub).foreach { subItem =>
                subItem.update(event)
            }
        } else {
            throw new NoSuchElementException("There is not the publisher!")
        }
    }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章