观察者模式(observer pattern):在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新。
观察者模式的结构
一个软件系统包含很多对象,就好比一个生态系统包含很多生物一样,生态系统里面的各种生物都彼此依赖和约束着,只要哪个生物的状态发生变化,其他的都会随之改变。
同样,一个软件系统常常要求在某一个对象的状态发生变化的时候,某些其他的对象做出相应的改变。做到这一点的设计方案有很多,但是为了使系统能够易于复用,应该选择低耦合度的设计方案。减少对象之间的耦合有利于系统的复用,但是同时设计师需要使这些低耦合度的对象之间能够维持行动的协调一致,保证高度的协作。观察者模式是满足这一要求的各种设计方案中最重要的一种。
下面就是观察者模式的最简单的框架:
观察者模式所涉及的角色有:
目标对象(Subject):目标对象把所有对观察者对象的引用保存在一个聚集(比如ArrayList对象)里,每个主题都可以有任何数量的观察者。目标对象提供一个接口,可以增加和删除观察者对象,目标对象又叫做抽象被观察者(Observable)角色。
具体的目标对象(ConcreteSubject):将有关状态存入具体观察者对象;在具体目标的内部状态改变时,给所有登记过的观察者发出通知。具体目标对象又叫做具体被观察者(Concrete Observable)角色。
观察者接口(Observer):为所有的具体观察者定义一个接口,在得到目标的通知时更新自己,这个接口叫做更新接口。
具体的观察者对象(ConcreteObserver):存储与目标的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与目标的状态相协调。如果需要,具体观察者角色可以保持一个指向具体目标对象的引用。
目标对象类(Subject):
/**
* 目标对象,它知道观察它的观察者,并提供注册(添加)和删除观察者的接口
* @author JUN
*
*/
public class Subject {
private List<Observer> observers = new ArrayList<Observer>();
//添加对象的方法
public void attach(Observer observer){
observers.add(observer);
}
//删除对象的方法
public void detach(Observer observer){
observers.remove(observer);
}
//通知所有注册的观察者对象
protected void notifyObserver(){
for (Observer observer : observers) {
observer.update(this);
}
}
}
具体的目标对象类(ConcreteSubject):
/**
* 具体的目标对象,负责把有关状态存入到相应的观察者对象中
* @author JUN
*
*/
public class ConcerteSubject extends Subject {
//目标对象的状态
private String subjectState;
public String getSubjectState() {
return subjectState;
}
public void setSubjectState(String subjectState) {
this.subjectState = subjectState;
this.notifyObserver();
}
}
观察者接口(Observer):
/**
* 这是一个观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的对象
* @author JUN
*
*/
public interface Observer {
//更新的接口
//传入目标对象,方便获取相应的目标对象状态
public void update(Subject subject);
}
具体的观察者对象(ConcreteObserver):
/**
* 具体的观察者对象,实现更新的方法,使自身的状态和目标的状态保持一致
* @author JUN
*
*/
public class ConcreteObserver implements Observer{
//观察者的状态
private String observerState;
//获取目标类的状态,同步到观察者状态中
@Override
public void update(Subject subject) {
observerState = ((ConcerteSubject)subject).getSubjectState();
}
}
以上是观察者模式的框架源码,以后只要是适合观察者的使用,就可以直接拿这个框架来修改使用。
接下来我们将结合一个非常形象的例子,来使用上面的观察者模式的源码框架。
小星是在气象台工作的,这段时间他正在追求一个女孩子,为了体现关心,小星把时时观察到的天气情况都告诉这个女孩子,并组织约会的时间和地点。要想更快的追求这个女孩子,那么久必须“收买”她妈,这个女孩子的妈妈喜欢逛街购物,逛街购物也需要好天气,这是小星也可以把天晴情况一并的更新给这个未来岳母。现在场景中的目标对象就是天气的情况,对天气的观察者就是小星要追的这个女孩子,以及她的老妈。
好接下来我们用代码实现一下:
天气的目标对象(WeatherSubject):
/**
* 目标对象,它知道观察它的观察者,并提供注册(添加)和删除观察者的接口
*
* @author JUN
*
*/
public class WeatherSubject {
// 用来保存注册的观察对象
private List<Observer> observers = new ArrayList<Observer>();
//把订阅天气的人添加到订阅者列表中
public void attach(Observer observer){
observers.add(observer);
}
/**
* 删除集合中的指定订阅天气的人
* @param observer
*/
public void detach(Observer observer){
observers.remove(observer);
}
/**
* 通知所有已经订阅了天气的人
*/
public void notifyObservers(){
for (Observer observer : observers) {
observer.update(this);
}
}
}
具体的天气目标对象(ConcreteWeatherSubject):
/**
* 具体的目标对象,负责把有关状态存入到相应的观察者对象中
* @author JUN
*
*/
public class ConcreteWeatherSubject extends WeatherSubject {
//获取天气的内容信息
private String weatherContent;
public String getWeatherContent() {
return weatherContent;
}
public void setWeatherContent(String weatherContent) {
this.weatherContent = weatherContent;
//内容有了,说明天气更新了,通知所有订阅的人
this.notifyObservers();
}
}
观察者接口(Observer):
/**
* 这是一个观察者接口。定义一个更新的接口给那些在目标发生改变的时候被通知的对象
* @author JUN
*
*/
public interface Observer {
/**
* 更新的接口
* @param subject 传入目标对象,方便获取相应的目标对象的状态
*/
public void update(WeatherSubject subject);
}
具体的观察者对象(ConcreteObserver):
/**
* 具体的观察者对象,实现更新的方法,使自身的状态和目标的状态保持一致
*
* @author JUN
*
*/
public class ConcreteObserver implements Observer {
// 观察者的名字,是谁收到了这个信息,小星的女朋友?还是她妈?
private String observerName;
//天气内容的情况,这个消息从目标出获取
private String weatherContent;
//提醒的内容:小星的女朋友提醒约会,而她老妈则提醒购物
private String remindThing;
/**
* 获取目标类的状态同步到观察者的状态中
*/
@Override
public void update(WeatherSubject subject) {
weatherContent = ((ConcreteWeatherSubject) subject).getWeatherContent();
System.out.println(observerName+"收到了"+weatherContent+","+remindThing);
}
public String getObserverName() {
return observerName;
}
public void setObserverName(String observerName) {
this.observerName = observerName;
}
public String getWeatherContent() {
return weatherContent;
}
public void setWeatherContent(String weatherContent) {
this.weatherContent = weatherContent;
}
public String getRemindThing() {
return remindThing;
}
public void setRemindThing(String remindThing) {
this.remindThing = remindThing;
}
}
接下来客户端类实现一下(Client):
/**
* 客户端
* @author JUN
*
*/
public class Client {
public static void main(String[] args) {
//1创建目标
ConcreteWeatherSubject weather = new ConcreteWeatherSubject();
//2创建观察者
ConcreteObserver observerGirl = new ConcreteObserver();
observerGirl.setObserverName("小星的女朋友");
observerGirl.setRemindThing("是我们的第一次约会,地点花城汇广场,不见不散!");
ConcreteObserver observerMum = new ConcreteObserver();
observerMum.setObserverName("未来岳母");
observerMum.setRemindThing("是一个购物的好日子,去北京路扫货!");
//3注册观察者
weather.attach(observerGirl);
weather.attach(observerMum);
//4目标发布天气
weather.setWeatherContent("明天天气晴朗,蓝天白云,气温26度");
}
}
运行结果:
观察者模式又分为两种模型:推模型和拉模型
推模型:目标对象主动向观察者推送目标的详细信息,推送的信息通常是目标对象的全部信息或部分信息。
拉模型:目标对象在通知观察者的时候,只传递少量的信息。如果观察者需要更新具体的信息,由观察者主动到目标对象中获取,相当于是观察者从目标对象中拉数据。一般这种模型的实现中,会把目标对象自身通过update方法传递给观察者。
根据以上的描述,你会发现上面的例子是典型的拉模型,下面我们把它改成推模型(红框就是改动的地方):
天气的目标对象(WeatherSubject):
具体的天气目标对象(ConcreteWeatherSubject):
观察者接口(Observer):
具体的观察者对象(ConcreteObserver):
客户端类(Client)不用改动,接下来运行一下,查看运行结果和上面的推模型一样的:
两种模型做比较:
推模型:
1.推模型是假定目标对象知道观察者需要的数据
2.推模型会使观察者对象难以复用
拉模型:
1.拉模型是目标对象不知道观察者具体需要什么数据,因此把自身传给观察者,由观察者来取值。
2.拉模型下,update方法的参数是目标对象本身,基本上可以适应各种情况的需要
其实Java中也提供了对观察者模式的实现
在JAVA语言的java.util库里面,提供了一个Observable类以及一个Observer接口,构成JAVA语言对观察者模式的支持。
上面的例子我们用Java提供的支持来实现一下:
天气目标的具体实现类(ConcreteWeatherSubject):
//天气目标的具体实现类
public class ConcreteWeatherSubject extends Observable {
//天气情况的内容
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
//天气情况有了,就要通知所有的观察者
//注意在通知之前,在用Java的Observer模式的时候,下面的这个调用是必不可少的
this.setChanged();
//然后就是主动通知,这里是推模型的方式
this.notifyObservers(content);
//拉模型的方式,我们就调用
// this.notifyObservers();
}
}
具体的观察者对象(ConcreteObserver):
//具体的观察者对象
public class ConcreteObserver implements Observer {
//具体的观察者对象
private String observerName;
/**
* o的参数是拉模型的参数,arg是推模型的参数
*/
@Override
public void update(Observable o, Object arg) {
//第一种是推的方式
System.out.println(observerName+"收到了消息,目标推送过来的是:"+arg);
//第二种是拉的方式
//System.out.println(observerName+"收到了消息,主动到目标中去拉,拉的内容是:"
// +((ConcreteWeatherSubject)o).getContent());
}
public String getObserverName() {
return observerName;
}
public void setObserverName(String observerName) {
this.observerName = observerName;
}
}
客户端实现类(Client):
public class Client {
public static void main(String[] args) {
//创建天气作为一个目标
ConcreteWeatherSubject subject = new ConcreteWeatherSubject();
//创建小星的女朋友作为观察者
ConcreteObserver girlFriend = new ConcreteObserver();
girlFriend.setObserverName("小星的女朋友");
//创建小星的未来岳母作为观察者
ConcreteObserver mum = new ConcreteObserver();
mum.setObserverName("小星的未来岳母");
//注册观察者
subject.addObserver(girlFriend);
subject.addObserver(mum);
//目标更新了天气
subject.setContent("天气晴朗,蓝天白云,气温26度");
}
}
运行结果(推模型):
运行结果(拉模型):
Java实现与自己实现的对比:
1、不需要再定义观察者和目标的接口了,JDK帮忙定义了;
2、具体的目标实现里面不需要再维护观察者的注册信息了,这个在Java中的Observable类里面已经帮忙实现好了;
3、触发通知的方式有一点变化,需要先调用setChanged方法,这个是Java为了帮助实现更精确的触发控制而提供的功能;
4、具体观察者的实现里面,update方法其实能同时支持推模型和拉模型,这个是Java在定义的时候,就已经考虑进去的。
观察者模式的优点:
1、观察者模式实现了观察者和目标之间的抽象耦合;
2、观察者模式实现了动态联动;
3、观察者模式支持广播通信。
缺点:可能会引起无谓的操作。
观察者适用场景:
1、当一个抽象模型有两个方面,其中一个方面的操作依赖于另一个方面的状态变化;
2、如果在更改一个对象的时候,需要同时连带改变其他的对象,而且不知道究竟应该有多少对象需要被连带改变;
3、当一个对象必须通知其他的对象,但是你又希望这个对象和其他被它通知的对象是松散耦合的。