设计模式-观察者模式
概念
观察者模式是基于订阅注册的方式,定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会受到通知并且自动更新,而观察者需要做的就是将自己注册到主题对象中,等待主题的通知。
设计
接口设计一般可设计成两个接口,一类接口为observer,另一类则为subject。
observer负责观察,接受通知,而subject负责注册、移除、通知等工作
因此我们创建两个接口,一个观察者,一个主题。
public interface Observer {
void update();
}
public interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
这里的remove和register需要参数传递,也就是注册的observer
特点-松耦合
当两个对象之间松耦合,它们依然可以交互,但是不太清楚彼此的细节。
观察者模式提供了一种对象设计,让主题和观察者之间松耦合。
observer只需要维护自身update实现,他能够得知的就是在每次需要更新的时候,update方法一定会被调用,而他只需要知道update要做什么即可,每种observer都可以有自己定制化的update实现
而subject不需要关注具体会有哪些观察者进行注册,他只需要维护一组观察者的集合,那么在需要通知的时候进行调用所有观察者的update方法,而具体如何执行则根据不同观察者中的策略来进行更新数据
数据的传输方式
在观察者模式中,存在两种可能的数据传递方式
主题推送给观察者
update中通过参数,由主题将需要的数据通过update方式推送给观察者,观察者从参数中获取数据。但是这种导致部分观察者也许不需要这些数据,造成接口不够灵活,但是对于主题来说比较方便,不需要暴露自身数据接口。另外,当需要添加新数据时,此种方法需要所有观察者一并修改接口来适应新的需要,还是有一些不方便的。
观察者主动索取数据
update不包含参数,主题提供一组getter可供观察者进行数据获取,这种方式使得接口变得更加灵活,需要的数据能够可选择的获得,不用在接口中写死,而后期需要增加数据也只需要添加getter,而不需要改动原有代码。一旦update方法被调用,那么observer自行去主题中获取数据。
java内置支持观察者模式
java的util包中内置了两个接口,一个是Observer,另一个是Observerable
/**
* A class can implement the <code>Observer</code> interface when it
* wants to be informed of changes in observable objects.
*
* @author Chris Warth
* @see java.util.Observable
* @since JDK1.0
*/
public interface Observer {
/**
* This method is called whenever the observed object is changed. An
* application calls an <tt>Observable</tt> object's
* <code>notifyObservers</code> method to have all the object's
* observers notified of the change.
*
* @param o the observable object.
* @param arg an argument passed to the <code>notifyObservers</code>
* method.
*/
void update(Observable o, Object arg);
}
观察者接口非常简单,一个update方法,其中传入参数为两个,Observable和Object。
Observable是我们所称的主题,Object 为数据。
将Observable传入update的用处是分辨主题,让观察者知道是哪个主题调用他让他进行修改的。因为一个观察者可能注册多个主题。
Observable是一个实现类,提供了一组方法来进行注册通知等操作
private boolean changed = false;
private Vector<Observer> obs;
/** Construct an Observable with zero Observers. */
public Observable() {
obs = new Vector<>();
}
其中,维护了观察者的集合是由Vector构成的,在构造函数中初始化一个空的容器。
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
提供了add和delete的方法,其中调用Vector的添加删除操作来进行对Observer的管理
public void notifyObservers(Object arg) {
/*
* a temporary array buffer, used as a snapshot of the state of
* current Observers.
*/
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
首先有几点。
第一:arrLocal,在通知之前将观察者们拷贝到本地变量中,因为我们可以看到后面的通知方式属于遍历的通知,这种如果在循环过程中,另有观察者前来注册,可能会导致循环出错,因此这里屏蔽后注册的观察者,而将当前状态的观察者进行通知。
第二:synchronize关键字没有放到方法上,而是采用代码块方式加锁。因为通知是一个遍历过程,需要时间,而synchronize加在方法上,会导致实例加锁,而所有的注册、修改、通知的方法均因monitor而阻塞在方法前,而我们之前说过,第一步就已经对arrLocal进行本地变量赋值了,因此我们只需要对赋值操作进行加锁,而遍历只是在本地变量中进行遍历,就不存在不安全问题,因此使用同步代码块的方式进行。
scala实现
最近在学scala,所以本次用scala对观察者模式进行实现
Observer接口
在scala中,Trait与java中的接口类似,因此声明一个名为Observer
的Trait
trait Observer {
def update(x: Any)
}
创建一个trait,并添加一个方法叫做update,参数x可以是任意值
再创建一个obserable
trait Observable {
def registerObserver(x: Observer)
def removeObserver(x: Observer)
def notifyObservers(args: Any)
}
这是另一个subject接口
import scala.collection.mutable.ArrayBuffer
class ScalaObservable(var data: Any) extends Observable {
private var observers = new ArrayBuffer[Observer]
def set(data:Any):Unit = {
notifyObservers(data)
}
override def registerObserver(x: Observer): Unit = {
observers += x
}
override def removeObserver(x: Observer): Unit = {
if (observers.indexOf(x) >= 0)
observers -= x
}
override def notifyObservers(args: Any): Unit = {
observers.foreach(_.update(args))
}
}
启动
object DpObserver {
def main(args: Array[String]): Unit = {
var ob1: Observer = new ObserverA
var subject: Observable = new ScalaObservable("aaa")
subject.registerObserver(ob1)
println("regist ob1")
println("notify observer")
println("===============")
subject.notifyObservers("gogogo")
}
}
运行结果