定义
定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动被更新。
别名
依赖(Dependents),发布-订阅(Publish-Subscribe)
动机
将一个系统分割成一系列相互协作的类有一个常见的副作用:需要维护相关对象间的一致性。我们不希望为了保持一致性而使各类紧密耦合,因为这样降低了它们的可重用性。
参与者
-
Subject(目标)
- 目标知道它的观察者。可以有任意多个观察者观察同一目标。
- 提供注册和删除观察者对象的接口。
-
Observer(观察者)
- 为那些在目标发生改变时需获得通知的对象定义一个更新接口。
-
ConcreteSubject(具体目标)
- 将有关状态存入各ConcreteObserver对象;
- 当它的状态发生改变时,向它的各个观察者发出通知。
-
ConcreteObserver(具体观察者)
- 维护一个指向ConcreteSubject对象的引用。
- 存储有关状态,这些状态应与目标的状态保持一致。
- 实现Observer的更新接口以使得=自身状态与目标的状态保持一致。
一对多模式
观察者模式流程图
以订阅天气为例
订阅者 :ObserverA|ObserverB 气象站 :(代表Subject)
观察者观察的对象是Subject.
- 黄明获取了最新的天气预报
- 黄明的女朋友设置了约会的时间地点
- 黄明的老妈则喜欢逛街,于是设置了购物提醒
- 这就是观察者收到通知以后,可以各自做出自己的反应。
观察者模式的结构图
ConcreteSubject是Subject接口的实现类。
学习观察者模式通用代码
步骤
- 第一.是目标对象的定义;
- 第二是具体的目标对象的定义
- 第三再来看看观察者的接口定义
- 最后来看看观察者的具体实现
目标对象
/**
* 目标对象,他知道它的观察者,并提供注册(添加)和删除观察者的接口
* */
public class Subject {
//用来保存注册的观察者对象
private List<Observer> observerList = new ArrayList<Observer>();
//添加观察者
public void attach(Observer observer){
observerList.add(observer);
}
//移除集合中指定的观察者
public void detach(Observer observer){
observerList.remove(observer);
}
/**
* 只有子类可以调用
* 向所有的注册观察者发送消息
* */
protected void notifyObservers(){
for( Observer observer: observerList){
observer.update(this);
}
}
}
ConcreteSubject
/**
* 具体的目标对象,负责把有关状态存入到相应的观察者对象中
* */
public class ConcreteSubject extends Subject{
//目标对象的状态
private String subjectState;
public String getSubjectState() {
return subjectState;
}
public void setSubjectState(String subjectState) {
this.subjectState = subjectState;
//状态发生改变通知每个观察者
this.notifyObservers();
}
}
Observer
/**
* 观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的对象
* */
public interface Observer {
/**
* 更新的接口
* @param subject 传入目标对象,方便获取相应的目标对象的状态
* */
void update(Subject subject);
}
ConcreteObserver
/**
* 具体的观察者对象,实现更新的方法,使自身的状态和目标保持一致
* */
public class ConcreteObserver implements Observer{
private String observerState;
/**
* 获取目标类的状态同步到观察者的状态中
* */
@Override
public void update(Subject subject) {
observerState = ((ConcreteSubject)subject).getSubjectState();
}
}
结合实际情况 :目标者为观察者提供服务;
WeatherSubject
/**
* 目标对象,他知道它的观察者,并提供注册(添加)和删除观察者的接口
* */
public class WeatherSubject {
//用来保存注册的观察者对象
private List<Observer> observerList = new ArrayList<Observer>();
/**
* 添加观察者
* 把订阅天气的人添加到订阅者列表中
* */
public void attach(Observer observer){
observerList.add(observer);
}
/**
* 移除集合中指定订阅天气的人
* */
public void detach(Observer observer){
observerList.remove(observer);
}
/**
* 只有子类可以调用
* 通知所有已经订阅天气的人
* */
protected void notifyObservers(){
for( Observer observer: observerList){
observer.update(this);
}
}
}
ConcreteWeatherSubject
/**
* 具体的目标对象,负责把有关状态存入到相应的观察者对象中
* */
public class ConcreteWeatherSubject extends WeatherSubject{
//获取天气的内容信息
private String waetherContent;
public String getWaetherContent() {
return waetherContent;
}
public void setWaetherContent(String waetherContent) {
this.waetherContent = waetherContent;
/**内容有了,说明天气更新了
* 状态发生改变通知每个观察者
* */
//this.notifyObservers();
this.notifyObservers(waetherContent);
}
}
Observer
/**
* 观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的对象
* */
public interface Observer {
/**
* 拉模型
* 更新的接口
* @param subject 传入目标对象,方便获取相应的目标对象的状态
* */
void update(WeatherSubject subject);
/**
*推模型
**/
void update(String content);
}
ConcreteObserver
/**
* 具体的观察者对象,实现更新的方法,使自身的状态和目标保持一致
* */
public class ConcreteObserver implements Observer{
//观察者的名字,是谁收到讯息
private String observerName;
//天气内容的情况,主打歌消息从目标处获取
private String weatherContent;
//提醒的内容
private String remindThing;
/**
* 获取目标类的状态同步到观察者的状态中
*
* */
@Override
public void update(WeatherSubject subject) {
//目标处的天气情况
weatherContent = ((ConcreteWeatherSubject)subject).getWaetherContent();
System.out.println("收件人:" + observerName + "\n\t" + weatherContent + "\n提醒事项:\n\t" + 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;
}
@Override
public void update(String content) {
weatherContent = content;
System.out.println("收件人:" + observerName + "\n\t" + weatherContent + "\n提醒事项:\n\t" + remindThing);
}
}
Client(测试端)
public class Client {
public static void main(String[] args) {
//1创建目标
ConcreteWeatherSubject weather = new ConcreteWeatherSubject();
//2 创建观察者
ConcreteObserver wife = new ConcreteObserver();
wife.setObserverName("gril friend");
wife.setRemindThing("第一次约会");
ConcreteObserver mother = new ConcreteObserver();
mother.setObserverName("mother");
mother.setRemindThing("Go Shopping!");
//3 注册观察者
weather.attach(wife);
weather.attach(mother);
// 4目标发布天气
weather.setWaetherContent("Sun,Good Weather! ");
}
}
认识观察者模式六大方面
目标与观察者之间的关系(一个观察者对应多个目标,一对一,一个目标对应多个观察者);
单向依赖(观察者依赖目标者);
命名建议:
- 目标接口的定义,建议在名称后面跟Subject;
- 观察者接口的定义,建议在名称后面跟Observer;
- 观察者接口的更新方法,建议名称为update)
- 触发通知的时机(完成状态维护后触发)
- 通知的顺序(通知的顺序是不确定的,是平行的)
观察者实现的两种方式
推模型和拉模型
推模型
目标对象主动向观察者推送目标的详细信息。 推送的信息通常是目标对象的全部或部分数据。
拉模型
目标对象在通知观察者的时候,只传递少量信息 如果观察者需要更具体的信息,由观察者主动到目标对象中获取,相当于是观察者从目标对象中拉数据。 一般这种模型的实现中,会把目标对象自身通过update方法传递给观察者。
两种模型比较
- 推模型是假定目标对象知道观察者需要的数据;
- 拉模型是目标对象不知道观察者具体需要什么数据,因此把自身传给观察者,由观察者来取值。
- 推模型会使观察者对象难以复用;
- 拉模型下,update方法的参数是目标对象本身,基本上可以适应各种情况的需要。
利用Java提供的观察者实现
- java.util包Observer类;
- 接口Observer update()方法;(观察者的接口)
与Java已有的实现比较
- 不需要再定义观察者和目标的接口了, JDK帮忙定义了
- 具体的目标实现里面不需要再维护观察者的注册信息了, 这个 在Java中的Observable类里面已经帮忙实现好了。
- 触发通知的方式有一点变化,要先调用setChanged方法,这 个是Java为了帮助实现更精确的触发控制而提供的功能。
- 具体观察者的实现里面, update方法其实能同时支持推模型 和拉模型,这个是Java在定义的时候,就已经考虑进去了。
Observer
/**
* 一个类可以实现Observer接口
* 希望被告知可观察对象的变化。
* */
public class ConcreteObserver implements Observer {
//观察者名称
private String observerName;
/**
* 每当观察对象发生变化时,都会调用此方法。
* @param o 目标的引用 (可观察的对象)采用拉方式
* @param arg 传递给notifyObservers的参数方法。
* */
@Override
public void update(Observable o, Object arg) {
//推方式
System.out.println("观察者:"+ observerName + " \n内容:\t" + arg);
//拉方式
System.out.println("观察者:"+ observerName + " \n主动去目标对象中去拉,拉的内容是\t" + ((ConcreteWeatherSubject)o).getContent());
}
public String getObserverName() {
return observerName;
}
public void setObserverName(String observerName) {
this.observerName = observerName;
}
}
Subject
/**
* 天气目标的具体实现类
* Observable:察觉到的
* */
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();
}
}
Client
public class Client {
public static void main(String [] args){
//创建天气作为一个目标(被观察者)
ConcreteWeatherSubject subject = new ConcreteWeatherSubject();
//创建观察者
ConcreteObserver observer = new ConcreteObserver();
observer.setObserverName("Jenkis");
ConcreteObserver observer1 = new ConcreteObserver();
observer1.setObserverName("Whaleson");
//注册观察者
subject.addObserver(observer);
subject.addObserver(observer1);
//通知天气更新
subject.setContent("天气:晴\n温度:2~6℃");
}
}
观察者优缺点
优点
- 观察者模式实现了观察者和目标之间的抽象耦合;
- 观察者模式实现了动态联动;
- 观察者模式支持广播通信;
缺点
可能引起无谓的操作;
观察者的模式:
触发联动
何时使用观察者模式
- 一个抽象模型有两个方面,其中-个方面的操作依赖于另一个方面的状态变化;
- 如果在更改一个对象的时候,需要同时连带改变其他的对象,而且不知道究竟应该有多少对象需要被连带改变;
- 当一个对象必须通知其他的对象,但是你又希望这个对象和其他被它通知的对象是松散耦合的。
区别对待观察者场景问题
需求总结
- A观察者只想接收下雨天的天气预报;
- B观察者想接收下雪天和下雨天的天气预报;
解决思路:
情况一:
如果是晴天,AB都不需要通知;
情况二:
如果天气下雨,AB都需要通知;
情况三:
如果下雪,A不通知,通知B;
代码解决区别对待观察者场景问题
实现
- 定义目标的抽象类和观察者的接;
- 口实现目标的类和观察者接口;
- 进行测试;
Observer
/**
* 定义一个更新的接口方法给那些在目标发生改变的时候被通知的观察者对象
* @author Administrator
*
*/
public interface Observer {
//更新的接口
void update(WeatherSubject weatherSubject);
//设置观察者名称
public void setObserverName(String observerName);
//取得观察者名称
public String getObserverName();
}
ConcreteObserver
public class ConcreteObserver implements Observer{
//观察者的名称
private String observerName;
//天气情况的内容
private String weatherContent;
//提醒的内容
private String remindContent;
@Override
public void update(WeatherSubject weatherSubject) {
weatherContent = ((ConcreteSubjectWeather)weatherSubject).getWeatherContent();
System.out.println( observerName + "收到了\n\t" +weatherContent + " \t" + remindContent );
}
@Override
public void setObserverName(String observerName) {
this.observerName = observerName;
}
@Override
public String getObserverName() {
return observerName;
}
public String getWeatherContent() {
return weatherContent;
}
public void setWeatherContent(String weatherContent) {
this.weatherContent = weatherContent;
}
public String getRemindContent() {
return remindContent;
}
public void setRemindContent(String remindContent) {
this.remindContent = remindContent;
}
}
Subject
public abstract class WeatherSubject {
//用来保存注册的观察者对象
public List<Observer> observers = new ArrayList<>();
//attach detach abstract notifyObserver
//将订阅天气的人添加到观察者列表
public void attach(Observer observer){
observers.add(observer);
}
//移除集合中指定的观察者
public void detach(Observer observer){
observers.remove(observer);
}
//区别对待观察者
protected abstract void notifyObserver();
}
ConcreteSubject
public class ConcreteWeatherSubject extends WeatherSubject {
//天气情况下的变量"晴天","下雨","下雪"
//目标对象的状态
private String weatherContent;
@Override
protected void notifyObserver() {
for(Observer observer : observers){
//如果是晴天 什么都不做
//下雨
if("下雨".equals(this.getWeatherContent())){
observer.update(this);
}
//下雪
if("下雪".equals(this.getWeatherContent())){
if("B".equals(observer.getObserverName())){
observer.update(this);
}
}
}
}
public void setWeatherContent(String weatherContent) {
this.weatherContent = weatherContent;
this.notifyObserver();
}
public String getWeatherContent() {
return weatherContent;
}
}
Client
public class Client {
public static void main(String[] args) {
//1.创建目标
ConcreteWeatherSubject weaSubject = new ConcreteWeatherSubject();
//2.创建观察者
ConcreteObserver a = new ConcreteObserver();
a.setObserverName("A");
a.setRemindContent("下雨了,出门带伞");
ConcreteObserver b = new ConcreteObserver();
b.setObserverName("B");
b.setRemindContent("天气恶劣,请勿出门");
//3.注册观察者
weaSubject.attach(a);
weaSubject.attach(b);
//4.目标发布天气
weaSubject.setWeatherContent("下雪");
}
}