第1章 概述
1-1 课程简介
观察者模式介绍
- 介绍观察者模式的应用(场景问题引出)
- 给出观察者模式这种解决方案
观察者模式详解
- 认识观察者模式
- 实现的两种方式:推模型和拉模型
- 利用Java提供的观察者模式
- 简述观察者优缺点
- 何时使用观察者模式
观察者模式衍射
- 区别对待观察者场景问题
- 代码示例解决场景问题
观察者模式的定义:定义对象的一种一对多的依赖关系。当一个对象的状态发生改变时,
所有依赖于它的对象都得到通知并被自动更新。
观察者模式流程图
黄明获取了最新的天气预报
黄明的女朋友设置了约会的时间地点
黄明的老妈 则喜欢逛街,于是设置了购物提醒
这就是 观察者收到通知以后,可以各自做出自己的反应
观察者模式介绍
- 课程目标和学习内容
- 介绍观察者模式的应用
- 观察者模式解决方案
第2章 观察者模式实战
2-1 观察者模式的结构和说明
Subject
- Attach(o:Object)
- Detach(o:Object)
- Notify()
Observer
- Update()
Subject维护一个Observer列表
2-2 学习观察者模式通用代码
- 目标对象的定义
- 具体目标对象的定义
- 观察者的接口定义
- 观察者的具体实现
/**
* 具体的观察者对象,实现更新的方法,使自身的状态和目标的状态保持一致
*/
public class ConcreteObserver implements Observer {
//观察者的状态
private String observerState;
@Override
public void update(Subject subject) {
observerState = ((ConcreteSubject)subject).getSubjectState();
}
}
/**
* 具体的目标对象,负责把有关状态存入到相应的观察者对象中
*/
public class ConcreteSubject extends Subject {
//目标对象的状态
private String subjectState;
public String getSubjectState() {
return subjectState;
}
public void setSubjectState(String subjectState) {
this.subjectState = subjectState;
this.notifyObservers();
}
}
/**
* 这是一个观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的对象
* @author leimj
* @date 2017年8月16日 下午4:56:25
*/
public interface Observer {
/**
* 更新的接口
* @param subject传入目标对象,方便获取相应的目标对象的状态
*/
public void update(Subject subject);
}
/**
* 目标对象,它知道观察它的观察者,并提供注册(添加)和删除观察者的接口
*/
public class Subject {
//用来保存注册的观察者对象
private List<Observer> observers = new ArrayList<Observer>();
//attach detach notifyObservers
public void attach(Observer observer){
observers.add(observer);
}
/**
* 删除集合中的指定观察者
* @param observer
*/
public void detach(Observer observer){
observers.remove(observer);
}
/**
* 通知所有注册的观察者对象
*/
protected void notifyObservers(){
for(Observer observer:observers){
observer.update(this);
}
}
}
2-3 改造通用代码解决场景问题
观察者对象所需变量
- 观察者的名字
- 天气情况的内容
- 提醒的内容
/**
* 目标对象,它知道观察它的观察者,并提供注册(添加)和删除观察者的接口
*/
public class WeatherSubject {
//用来保存注册的观察者对象
private List<Observer> observers = new ArrayList<Observer>();
//attach detach notifyObservers
//把订阅天气的人添加到订阅者列表中
public void attach(Observer observer){
observers.add(observer);
}
/**
* 删除集合中的指定订阅天气的人
* @param observer
*/
public void detach(Observer observer){
observers.remove(observer);
}
/**
* 通知所有已经订阅了天气的人
*/
protected void notifyObservers(){
for(Observer observer:observers){
observer.update(this);
}
}
}
/**
* 具体的目标对象,负责把有关状态存入到相应的观察者对象中
*/
public class ConcreteWeatherSubject extends WeatherSubject {
//获取天气的信息
private String weatherContent;
public String getWeatherContent() {
return weatherContent;
}
public void setWeatherContent(String weatherContent) {
this.weatherContent = weatherContent;
//内容有了,说明天气更新了,通知所有的订阅的人
this.notifyObservers();
}
}
/**
* 这是一个观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的对象
* @author leimj
* @date 2017年8月16日 下午4:56:25
*/
public interface Observer {
/**
* 更新的接口
* @param subject传入目标对象,方便获取相应的目标对象的状态
*/
public void update(WeatherSubject subject);
}
/**
* 具体的观察者对象,实现更新的方法,使自身的状态和目标的状态保持一致
*/
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;
}
}
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("明天天气晴朗,蓝天白云,气温28度");
}
}
第3章 观察者模式详解
3-1 认识观察者模式
- 目标与观察者之间的关系
- 一对多的关系
- 一个观察者也可以观察多个目标(多对一)
- 单向依赖
- 观察者依赖目标
- 命名建议
- 目标接口的定义,建议在名称后面跟Subject
- 观察者接口的定义,建议在名称后面跟Observer
- 观察者接口的更新方法,建议名称为update
- 触发通知的时机
- 先设置值再通知
- 观察者模式的调用顺序示意图
- 通知的顺序
- 不依赖于顺序,多个观察者之间是平行的
3-2 实现的两种方式:推模型和拉模型
- 推模型
目标对象主动向观察者推送目标的详细信息
推送的信息通常是目标对象的全部或部分数据 - 拉模型
目标对象在通知观察者的时候,只传递少量的信息
如果观察者需要更具体的信息,由观察者主动到目标对象中获取,相当于是观察者从目标对象中拉数据
一般这种模型的实现中,会把目标对象自身通过update方法传递给观察者
/**
* 目标对象,它知道观察它的观察者,并提供注册(添加)和删除观察者的接口
*/
public class WeatherSubject {
//用来保存注册的观察者对象
private List<Observer> observers = new ArrayList<Observer>();
//attach detach notifyObservers
//把订阅天气的人添加到订阅者列表中
public void attach(Observer observer){
observers.add(observer);
}
/**
* 删除集合中的指定订阅天气的人
* @param observer
*/
public void detach(Observer observer){
observers.remove(observer);
}
/**
* 通知所有已经订阅了天气的人
*/
protected void notifyObservers(String content){
for(Observer observer:observers){
observer.update(content);
}
}
}
/**
* 具体的目标对象,负责把有关状态存入到相应的观察者对象中
*/
public class ConcreteWeatherSubject extends WeatherSubject {
//获取天气的信息
private String weatherContent;
public String getWeatherContent() {
return weatherContent;
}
public void setWeatherContent(String weatherContent) {
this.weatherContent = weatherContent;
//内容有了,说明天气更新了,通知所有的订阅的人
this.notifyObservers(weatherContent);
}
}
/**
* 这是一个观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的对象
* @author leimj
* @date 2017年8月16日 下午4:56:25
*/
public interface Observer {
/**
* 更新的接口
* @param subject传入目标对象,方便获取相应的目标对象的状态
*/
public void update(String content);
}
/**
* 具体的观察者对象,实现更新的方法,使自身的状态和目标的状态保持一致
*/
public class ConcreteObserver implements Observer {
//观察者的名字,是谁收到了这个讯息,黄明的女朋友还是他老妈
private String observerName;
//天气内容的情况,这个消息从目标处获取
private String weatherContent;
//提醒的内容:黄明的女朋友提醒约会,而他老妈则提醒购物
private String remindThing;
/**
* 获取目标类的状态同步到观察者的状态中
*/
@Override
public void update(String content) {
// weatherContent = ((ConcreteWeatherSubject)subject).getWeatherContent();
weatherContent = content;
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;
}
}
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("明天天气晴朗,蓝天白云,气温28度");
}
}
比较
- 推模型是假定目标对象知道观察者需要的数据
- 拉模型是目标对象不知道观察者具体需要什么数据,因此把自身传递给观察者,由观察者来取值
- 推模型会使观察者对象难以复用
- 拉模型下,update方法的参数是目标本身,基本上可以适应各种情况的需求
3-3 利用Java提供的观察者实现
java.util包
Observable类
接口Observer
update()
- 不需要再定义观察者和目标的接口了,JDK帮忙定义了
- 具体的目标实现里不需要再维护观察者的注册信息了,这个在Java中的Observable类里面已经帮忙实现好了
- 触发通知的方法有一点变化,要先调用setChanged方法,这个是Java为了帮助实现更精确的触发控制而提供的功能
- 具体观察者的实现里面,update方法其实能同时支持推模型和拉模型,这个是Java在定义的时候,就已经考虑进去了
//天气目标具体实现类
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();
}
}
//具体的观察者对象
public class ConcreteObserver implements Observer {
//观察者名称变量
private String observerName;
public String getObserverName() {
return observerName;
}
public void setObserverName(String observerName) {
this.observerName = observerName;
}
@Override
public void update(Observable o, Object arg) {
//第一种是推的方式
System.out.println(observerName + "收到了消息,目标推送过来的是" + arg);
//第二种是拉的方式
System.out.println(observerName + "收到了消息,主动到目标的对象中去拉,拉的内容是"
+ ((ConcreteWeatherSubject)o).getContent());
}
}
//测试类
public class Client {
public static void main(String[] args) {
//创建天气作为一个目标,也可以说是被观察者
ConcreteWeatherSubject subject = new ConcreteWeatherSubject();
//创建黄明的女朋友作为观察者
ConcreteObserver girl = new ConcreteObserver();
girl.setObserverName("黄明的女朋友");
//创建黄明的老妈作为观察者
ConcreteObserver mum = new ConcreteObserver();
mum.setObserverName("黄明的老妈");
//注册观察者
subject.addObserver(girl);
subject.addObserver(mum);
//目标更新天气情况了
subject.setContent("天气晴,气温28度");
}
}
3-4 观察者优缺点
- 观察者的优点
- 观察者模式实现了观察者和目标之间的抽象耦合
- 观察者模式实现了动态联动
- 观察者模式支持广播通信
- 观察者的缺点
- 可能会引起无谓的操作
3-5 何时使用观察者模式
触发联动
观察者模式的本质
建议在以下情况中选用观察者模式
- 当一个抽象模型有两个方面,其中一个方面的操作依赖于另一个方面的状态变化
- 如果在更改一个对象的时候,需要同时连带改变其他的对象,而且不知道究竟应该有多少对象需要被连带改变
- 当一个对象必须通知其他的对象,但是你又希望这个对象和其它通知的对象是松散耦合的
第4章 观察者模式衍生
4-1 区别对待观察者场景问题
需求总结
- 黄明的女朋友 只想接收 下雨的天气预报
- 黄明的老妈 想接收 下雨或者下雪的天气预报
- 情况之一
如果天气是晴天
她们俩就不需要进行通知了 - 情况之二
如果天气是下雨
则黄明的女朋友需要通知
而黄明的老妈也需要通知 - 情况之三
如果是下雪
只通知黄明老妈
4-2 代码示例解决场景问题
示例的实现步骤:
1. 定义目标的抽象类和观察者的接口
2. 实现目标类和观察者接口
3. 进行测试
public abstract class WeacherSubject {
//用来保存注册观察者对象
public List<Observer> observers = new ArrayList<Observer>();
//attach detach abstract notifyObservers
//把订阅天气的人/观察者添加到订阅者列表中
public void attach(Observer observer){
observers.add(observer);
}
//删除集合中指定的订阅天气的人
public void detach(Observer observer){
observers.remove(observer);
}
protected abstract void notifyObservers();
}
4-3 观察者接口
//定义一个更新的接口方法给那些在目标发生改变的时候被通知的观察者对象调用
public interface Observer {
//更新的接口
public void update(WeatherSubject subject);
//设置观察者名称
public void setObserverName(String observerName);
//取得观察者名称
public String getObserverName();
}
4-4 目标实现类
public class ConcreteWeatherSubject extends WeatherSubject {
//"晴天" "下雨" "下雪"
//目标对象的状态
private String weatherContent;
@Override
protected void notifyObservers() {
//循环所有注册的观察者
for(Observer observer:observers){
//规则是:
//黄明的女朋友 需要 "下雨" 的条件通知,其他不通知
//黄明的老妈需要 "小雨" 或者 "下雪" 的条件通知,其他条件不通知
//如果天气是晴
//do nothing...
//如果天气是下雨
if("下雨".equals(this.getWeatherContent())){
if("黄明的女朋友".equals(observer.getObserverName())){
observer.update(this);
}
if("黄明的老妈".equals(observer.getObserverName())){
observer.update(this);
}
}
//如果天气是下雪
if("下雪".equals(this.getWeatherContent())){
if("黄明的老妈".equals(observer.getObserverName())){
observer.update(this);
}
}
}
}
public String getWeatherContent() {
return weatherContent;
}
public void setWeatherContent(String weatherContent) {
this.weatherContent = weatherContent;
this.notifyObservers();
}
}
4-5 观察者接口类的实现
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);
}
@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 getRemindThing() {
return remindThing;
}
public void setRemindThing(String remindThing) {
this.remindThing = remindThing;
}
}
4-6 测试
public class Client {
public static void main(String[] args) {
//1 创建目标
ConcreteWeatherSubject weatherSubject = new ConcreteWeatherSubject();
//2 创建观察者
ConcreteObserver observerGirl = new ConcreteObserver();
observerGirl.setObserverName("黄明的女朋友");
observerGirl.setRemindThing("下雨了,安静的呆在家里吧");
ConcreteObserver observerMum = new ConcreteObserver();
observerMum.setObserverName("黄明的老妈");
observerMum.setRemindThing("不管是下雨还是下雪,我都不出门了");
//3 注册观察者
weatherSubject.attach(observerGirl);
weatherSubject.attach(observerMum);
//4 目标发布天气了
weatherSubject.setWeatherContent("下雨");
}
}
第5章 课程总结
5-1 课程总结
- 观察者模式介绍
场景描述 - 观察者模式实战
如何使用观察者模式化解黄明的烦恼 - 观察者模式详解
推模型和拉模型
观察者模式的优缺点
何时使用观察者模式 - 观察者模式衍生
使用区别对待观察者模式的方法来解决场景问题