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