慕课网《模式的秘密之观察者模式》学习总结
时间:2017年08月29日星期二
说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com
教学源码:https://github.com/zccodere/study-imooc
学习源码:https://github.com/zccodere/study-imooc
第一章:观察者模式概述
1-1 课程简介
观察者模式的定义
定义对象间的一种一对多的依赖关系。
当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
案例流程图
第二章:观察者模式实战 2-1 结构类图观察者模式结构
观察者模式类图
2-2 通用代码实现步骤
1.目标对象的定义
2.具体的目标对象的定义
3.观察者的接口定义
4.观察者的具体实现
代码编写
1.编写Subject类
package com.myimooc.designpattern.c5observer.common;
import java.util.ArrayList;
import java.util.List;
/**
* @describe 目标类,目标对象,它知道观察它的观察者,并提供注册(添加)和删除观察者的接口
* @author zc
* @version 1.0 2017-08-29
*/
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() {
observers.forEach(observer ->{
observer.update(this);
});
}
}
2.编写ConcreteSubject类
package com.myimooc.designpattern.c5observer.common;
/**
* @describe 具体的目标对象,负责把有关状态存入到相应的观察者对象中
* @author zc
* @version 1.0 2017-08-29
*/
public class ConcreteSubject extends Subject {
/**
* 目标对象的状态
*/
private String subjectState;
public String getSubjectState() {
return subjectState;
}
public void setSubjectState(String subjectState) {
this.subjectState = subjectState;
// 当状态发生改变时,通知观察者
this.notifyObserver();
}
}
3.编写Observer类
package com.myimooc.designpattern.c5observer.common;
/**
* @describe 观察者接口,定义一个更新的接口给那些在目标对象发生改变的时候被通知的对象
* @author zc
* @version 1.0 2017-08-29
*/
public interface Observer {
/**
* 更新的接口
* @param subject 传入的目标对象,方便获取相应的目标对象的状态
*/
void update(Subject subject);
}
4.编写ConcreteObserver类
package com.myimooc.designpattern.c5observer.common;
/**
* @describe 具体的观察者对象,实现更新的方法,使自身的状态和目标的状态保持一致
* @author zc
* @version 1.0 2017-08-29
*/
public class ConcreteObserver implements Observer {
/**
* 观察者的状态
*/
private String observerState;
/**
* 获取目标类的状态同步到观察者的状态中
*/
@Override
public void update(Subject subject) {
observerState = ((ConcreteSubject)subject).getSubjectState();
}
public String getObserverState() {
return observerState;
}
}
2-3 订阅天气
代码编写
1.编写WeatherSubject类
package com.myimooc.designpattern.c5observer.weather;
import java.util.ArrayList;
import java.util.List;
/**
* @describe 管理订阅者列表
* @author zc
* @version 1.0 2017-08-29
*/
public class WeatherSubject {
/**
* 订阅者列表
*/
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() {
observers.forEach(observer ->{
observer.update(this);
});
}
}
2.编写ConcreteWeatherSubject类
package com.myimooc.designpattern.c5observer.weather;
/**
* @describe 具体的目标对象,负责把有关状态存入到相应的观察者对象中
* @author zc
* @version 1.0 2017-08-29
*/
public class ConcreteWeatherSubject extends WeatherSubject {
/**
* 获取天气的内容信息
*/
private String weatherContent;
public String getWeatherContent() {
return weatherContent;
}
public void setWeatherContent(String weatherContent) {
this.weatherContent = weatherContent;
// 内容有了,说明天气更新了,通知所有订阅的人
this.notifyObserver();
}
}
3.编写Observer类
package com.myimooc.designpattern.c5observer.weather;
/**
* @describe 观察者接口,定义一个更新的接口给那些在目标对象发生改变的时候被通知的对象
* @author zc
* @version 1.0 2017-08-29
*/
public interface Observer {
/**
* 更新的接口
* @param subject 传入的目标对象,方便获取相应的目标对象的状态
*/
void update(WeatherSubject subject);
}
4.编写ConcreteObserver类
package com.myimooc.designpattern.c5observer.weather;
/**
* @describe 具体的观察者对象,实现更新的方法,使自身的状态和目标的状态保持一致
* @author zc
* @version 1.0 2017-08-29
*/
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;
}
}
5.编写Client类
package com.myimooc.designpattern.c5observer.weather;
/**
* @describe 订阅天气-测试类
* @author zc
* @version 1.0 2017-08-29
*/
public class Client {
public static void main(String[] args) {
// 1.创建目标
ConcreteWeatherSubject weather = new ConcreteWeatherSubject();
// 2.创建观察者
ConcreteObserver observerGiel = new ConcreteObserver();
observerGiel.setObserverName("黄明的女朋友");
observerGiel.setRemindThing("是我们的第一次约会,地点街心公园,不见不散哦");
ConcreteObserver observerMum = new ConcreteObserver();
observerMum.setObserverName("老妈");
observerMum.setRemindThing("是一个购物的好日子,明天去天虹扫货");
// 3.注册观察者
weather.attach(observerGiel);
weather.attach(observerMum);
// 4.目标发布天气
weather.setWeatherContent("明天 天气晴朗,蓝天白云,气温28℃");
}
}
第三章:观察者模式详解
3-1 深入认识
目标与观察者之间的关系
一对多的关系
一对一的关系(如果观察者只有一个)
单向依赖
在观察者模式中,观察者和目标是单向依赖,只有观察者依赖目标,而不是目标依赖观察者。
主动权掌握在目标手中,只有目标知道什么时候需要通知观察者。
命名建议
观察者模式又被称为发布订阅模式
目标接口的定义,建议在名称后面跟Subject
观察者接口的定义,建议在名称后面跟Observer
观察者接口的更新方法,建议名称为uodate
触发通知的时机
一般情况下,是在完成了状态维护后触发。
因为通知会传递数据,不能先通知,后改数据,这会导致观察者和目标对象状态不一致。
观察者模式的调用顺序示意图-准备阶段
运行阶段
通知的顺序
从理论上来说,当目标对象的状态发生改变时,通知所有观察者的时候,顺序是不确定的。
因此,观察者实现的功能,绝对不能依赖于通知的顺序。
也就是说,多个观察者之间的顺序是平行的,相互不应该有先后依赖的关系。
3-2 推拉模型
推模型
目标对象主动向观察者推送目标的详细信息
推送的信息通常是目标对象的全部或部分数据
相当于是在广播通讯
拉模型(第二章的实现属于拉模型)
目标对象在通知观察者的时候,只传递少量信息
如果观察者需要更具体的信息,由观察者主动到目标对象中获取
相当于是观察者从目标对象中拉数据
一般这种模型的实现中,会把目标对象自身通过update方法传递给观察者
两种模型的区别
推模型是假定目标对象知道观察者需要的数据
推模型会使观察者对象难以复用
拉模型是目标对象不知道观察者具体需要什么数据,因此把自身传给观察者,由观察者来取值
拉模型下,update方法的参数是目标对象本身,基本上可以适应各种情况的需要
3-3 Java实现
Java实现与自己实现的对比
不需要再定义观察者和目标的接口了,JDK定义好了
具体的目标实现里面不需要再维护观察者的注册信息了,JDK在Observable类里面实现好了
触发通知的方式有一点变化,要先调用setChanged方法,这是为了实现更精确的触发控制
具体观察者的实现里面,update方法能同时支持推模型和拉模型
代码编写
1.编写ConcreteWeatherSubject类
package com.myimooc.designpattern.c5observer.weatherjdk;
import java.util.Observable;
/**
* @describe 使用JDK实现观察者模式,天气目标具体实现类
* @author zc
* @version 1.0 2017-08-29
*/
public class ConcreteWeatherSubject extends Observable {
/** 天气情况的内容 */
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
// 天气情况有了,就要通知所有的观察者
// 在用Java中的Observer模式时,需要先调用setChanged方法
this.setChanged();
// 调用通知方法-推模型
this.notifyObservers(content);
// 调用通知方法-拉模型
// this.notifyObservers();
}
}
2.编写ConcreteObserver类
package com.myimooc.designpattern.c5observer.weatherjdk;
import java.util.Observable;
import java.util.Observer;
/**
* @describe 使用JDK实现观察者模式,具体的观察者对象
* @author zc
* @version 1.0 2017-08-29
*/
public class ConcreteObserver implements Observer {
/** 观察者的名称,是谁收到了这个信息 */
private String observerName;
@Override
public void update(Observable o, Object arg) {
// 推模型
System.out.println(observerName + " 收到了消息,目标推送过来的是 "+arg);
// 拉模型
ConcreteWeatherSubject concreteWeatherSubject = (ConcreteWeatherSubject)o;
System.out.println(observerName + " 收到了消息,主动到目标对象中去拉 "+concreteWeatherSubject.getContent());
}
public String getObserverName() {
return observerName;
}
public void setObserverName(String observerName) {
this.observerName = observerName;
}
}
3.编写Client类
package com.myimooc.designpattern.c5observer.weatherjdk;
/**
* @describe 使用JDK实现观察者模式,测试类
* @author zc
* @version 1.0 2017-08-29
*/
public class Client {
public static void main(String[] args) {
// 创建天气作为一个目标,也可以说是被观察者
ConcreteWeatherSubject subject = new ConcreteWeatherSubject();
// 创建黄明的女朋友作为观察者
ConcreteObserver observerGiel = new ConcreteObserver();
observerGiel.setObserverName("黄明的女朋友");
// 创建黄明的老妈作为观察者
ConcreteObserver observerMum = new ConcreteObserver();
observerMum.setObserverName("老妈");
// 注册观察者
subject.addObserver(observerGiel);
subject.addObserver(observerMum);
// 目标更新天气情况
subject.setContent("明天 天气晴朗,蓝天白云,气温28℃");
}
}
3-4 优点缺点
简述观察者优缺点
优点
观察者模式实现了观察者和目标之间的抽象耦合
观察者模式实现了动态联动(所谓联动是指做一个操作会引起其它相关的操作)
观察者模式支持广播通信
缺点
可能会引起无谓的操作
3-5 何时使用
观察者模式的本质
触发联动
建议在以下情况中选用观察者模式
当一个抽象模型有两个方面,其中一个方面的操作依赖于另一个方面的状态变化
如果在更改一个对象的时候,需要同时连带改变其他的对象,
而且不知道究竟应该有多少对象需要被连带改变
当一个对象必须通知其他的对象,但是又希望这个对象和被它通知的对象是松散耦合的
第四章:观察者模式衍生
4-1 特殊场景
需求总结
区别对待观察者
需要根据不同的天气情况来通知不同的观察者
黄明的女朋友只想接收 下雨的天气预报
黄明的老妈想接收 下雨或者下雪的天气预报
解决思路
当天气更新时,在目标天气中进行判断,如果不符合观察者的条件,则不进行通知
4-2 代码示例
实现步骤
1.定义目标的抽象类和观察者的接口
2.实现目标的类和观察者接口
3.编写测试类进行测试
代码编写
1.编写WeatherSubject类
package com.myimooc.designpattern.c5observer.weathercondition;
import java.util.ArrayList;
import java.util.List;
/**
* @describe 天气目标抽象类
* @author zc
* @version 1.0 2017-08-29
*/
public abstract class WeatherSubject {
/** 用来保存注册的观察者对象 */
protected List<Observer> observers = new ArrayList<Observer>();
/**
* 增加观察者
*/
public void attach(Observer observer){
observers.add(observer);
}
/**
* 删除观察者
*/
public void detach(Observer observer){
observers.remove(observer);
}
/**
* 区别通知观察者-由子类实现
*/
protected abstract void notifyObservers();
}
2.编写Observer类
package com.myimooc.designpattern.c5observer.weathercondition;
/**
* @describe 观察者接口,定义一个更新的接口给那些在目标对象发生改变的时候被通知的对象
* @author zc
* @version 1.0 2017-08-29
*/
public interface Observer {
/**
* 更新的接口
* @param subject 传入的目标对象,方便获取相应的目标对象的状态
*/
void update(WeatherSubject subject);
/** 设置观察者名称 */
void setObserverName(String observerName);
/** 获取观察者名称 */
String getObserverName();
}
3.编写ConcreteWeatherSubject类
package com.myimooc.designpattern.c5observer.weathercondition;
import java.util.Objects;
/**
* @describe 天气目标的实现类
* @author zc
* @version 1.0 2017-08-29
*/
public class ConcreteWeatherSubject extends WeatherSubject {
// 天气情况:晴天、下雨、下雪
// 目标对象的状态
private String weatherContent;
@Override
protected void notifyObservers() {
// 遍历所有注册的观察者
this.observers.forEach(observer -> {
// 规则是:
// 黄明的女朋友 需要 下雨 的条件通知,其他条件不通知
// 黄明的老妈 需要 下雨 或者 下雪 的条件通知,其他条件不通知
// 如果天气是晴天
// do nothing...
// 如果天气是下雨
if(Objects.equals("下雨", this.getWeatherContent())){
if(Objects.equals("黄明的女朋友", observer.getObserverName())){
observer.update(this);
}
if(Objects.equals("黄明的老妈", observer.getObserverName())){
observer.update(this);
}
}
// 如果天气是下雪
if(Objects.equals("下雪", this.getWeatherContent())){
if(Objects.equals("黄明的老妈", observer.getObserverName())){
observer.update(this);
}
}
});
}
public String getWeatherContent() {
return weatherContent;
}
public void setWeatherContent(String weatherContent) {
this.weatherContent = weatherContent;
this.notifyObservers();
}
}
4.编写ConcreteObserver类
package com.myimooc.designpattern.c5observer.weathercondition;
/**
* @describe 观察者的实现类
* @author zc
* @version 1.0 2017-08-29
*/
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 String getObserverName() {
return observerName;
}
@Override
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;
}
}
5.编写Client类
package com.myimooc.designpattern.c5observer.weathercondition;
/**
* @describe 区别对待观察者测试类
* @author zc
* @version 1.0 2017-08-29
*/
public class Client {
public static void main(String[] args) {
// 1.创建目标
ConcreteWeatherSubject weather = new ConcreteWeatherSubject();
// 2.创建观察者
ConcreteObserver observerGiel = new ConcreteObserver();
observerGiel.setObserverName("黄明的女朋友");
observerGiel.setRemindThing("下雨了,安静的呆在家里吧");
ConcreteObserver observerMum = new ConcreteObserver();
observerMum.setObserverName("黄明的老妈");
observerMum.setRemindThing("不管下雨还是下雪,我都不出门了");
// 3.注册观察者
weather.attach(observerGiel);
weather.attach(observerMum);
// 4.目标发布天气
weather.setWeatherContent("天气");
weather.setWeatherContent("下雪");
weather.setWeatherContent("下雨");
}
}
第五章:观察者模式总结
5-1 课程总结
总结
观察者模式简介:场景描述
观察者模式实战:模式原理
观察者模式详解:推拉模型、JDK实现、优缺点
观察者模式衍生:区别对待观察者