观察者模式重点在于 观察者和被观察者的对应关系,以及将被观察者的改变及时通知到相对应的观察者。
这样的模式基本上可以解决少量数据源的情景,在观察者和被观察者可能是多对多关系的情况下,强耦合的结构会让代码不够清晰,难以维护。
在《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!")
}
}
}