点赞再看,养成习惯,公众号搜一搜【一角钱技术】关注更多原创技术文章。
本文 GitHub org_hejianhui/JavaStudy 已收录,有我的系列文章。
前言
- 23种设计模式速记
- 单例(singleton)模式
- 工厂方法(factory method)模式
- 抽象工厂(abstract factory)模式
- 建造者/构建器(builder)模式
- 原型(prototype)模式
- 享元(flyweight)模式
- 外观(facade)模式
- 适配器(adapter)模式
- 装饰(decorator)模式
- 持续更新中…
23种设计模式快速记忆的请看上面第一篇,本篇和大家一起来学习观察者模式相关内容。
模式定义
定义了对象之间的一对多依赖,让多个观察者对象同时监听某一个主题对象,当主题对象发生变化时,它的所有依赖者都会收到通知并更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
观察者模式是对象之间一对多的一种模式,被依赖的对象是Subject,依赖的对象是Observer,Subject通知Observer变化,Subject为1,Observer为多。
解决的问题
一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
模式组成
组成(角色) | 作用 |
---|---|
抽象主题角色(Subject) | 把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。 |
抽象观察者角色(Observer) | 为所有具体的观察者定义一个接口,在得到主题的通知时更新自己。 |
具体主题角色(Subject1) | 在具体主题内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个子类实现。 |
具体观察者角色(Observer1) | 该角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。通常用一个子类实现。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。 |
实例说明
实例概况
某天下午班主任通知某班学生和老师将要听一节课,以此来对老师的授课质量进行评分。
- 老师和学生收到后开始安排相关的课程;
- 上课期间老师和班主任通过观察学生的神情来预判课程的讲的好坏
- 老师观察到学生皱眉头可以适当调节课程气氛
- 班主任观察到学生课堂氛围好转,给与高分
- 课程结束后,班主任和老师回到办公室,一起回顾这节开心的课程。
使用步骤
步骤1:构建一个课程实体类
class Course {
// 上课时间:time
private Date time;
// 上课地点:place
private String place;
// 上课内容:content
private String content;
// 省略get/set...
public Course() {
}
public Course(Date time, String place, String content) {
this.time = time;
this.place = place;
this.content = content;
}
}
步骤2:构建一个发现者的抽象类以及相关的实现类,老师和班主任都分别继承了该接口并重写相关方法
abstract class Observer {
abstract void update(Object args);
public Observer(String identity) {
this.identity = identity;
}
private String identity;
public String getIdentity() {
return identity;
}
public void setIdentity(String identity) {
this.identity = identity;
}
}
步骤3:创建一个具体的观察者角色,老师拿着教材开始来上课
/**
* 老师类
* - 观察者之一
* - 观察学生的上课情况
*/
class TeacherObserver extends Observer {
private Course course;
@Override
public void update(Object args) {
DateFormat df = DateFormat.getTimeInstance(DateFormat.LONG, Locale.CHINA);
System.out.println("我是王老师,正在讲课中...");
course = new Course(new Date(), "A栋教学楼", "高等数学");
System.out.println("今天上课时间:" + df.format(course.getTime()) + " 地点:" + course.getPlace() + " 上课内容:" + course.getContent());
}
public TeacherObserver(String identity) {
super(identity);
}
}
步骤4:创建一个具体的观察者角色,班主任来听课
/**
* 班主任来听课
* - 观察者之一
* - 观察学生的上课情况
*/
class HeadTeacherObserver extends Observer {
@Override
public void update(Object args) {
System.out.println("我是班主任来听课了,正在检查课程质量...");
System.out.println("学生反馈课程质量为:" + args);
}
public HeadTeacherObserver(String identity) {
super(identity);
}
}
步骤5:创建被观察者抽象主题角色
/**
* 主体类
* - 模拟被观察者主体
*/
abstract class Subject {
/**
* 修改通知
*/
abstract void doNotify();
/**
* 添加被观察者
*/
abstract void addObservable(Observer o);
/**
* 移除被观察者
*/
abstract void removeObservable(Observer o);
}
步骤6:创建具体的被观察者主体角色,学生主体为被观察对象
/**
* 学生主体
* - 被观察的对象
*/
class StudentSubject extends Subject {
/**
* 上课状态
*/
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
private List<Observer> observableList = new ArrayList<>();
@Override
public void doNotify() {
for (Observer observer : observableList) {
observer.update(state);
}
}
@Override
public void addObservable(Observer observable) {
observableList.add(observable);
}
@Override
public void removeObservable(Observer observable) {
try {
if (observable == null) {
throw new Exception("要移除的被观察者不能为空");
} else {
if (observableList.contains(observable) ) {
System.out.println("下课了,"+observable.getIdentity()+" 已回到办公室");
observableList.remove(observable);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
步骤7:开始上课,开始记录报告
/**
* 观察者模式
*/
public class ObserverPattern {
public static void main(String[] args) {
// 创建学生主体
StudentSubject studentSubject = new StudentSubject();
// 创建观察者老师
TeacherObserver teacherObversable = new TeacherObserver("王老师");
// 创建观察者班主任
HeadTeacherObserver headTeacherObserver = new HeadTeacherObserver("班主任");
// 学生反映上课状态
studentSubject.setState("讲的不错,很好!");
studentSubject.addObservable(teacherObversable);
studentSubject.addObservable(headTeacherObserver);
// 开始上课
studentSubject.doNotify();
// 上课结束
studentSubject.removeObservable(headTeacherObserver);
studentSubject.removeObservable(teacherObversable);
}
}
输出结果
我是王老师,正在讲课中...
今天上课时间:下午11时57分01秒 地点:A栋教学楼 上课内容:高等数学
我是班主任来听课了,正在检查课程质量...
学生反馈课程质量为:讲的不错,很好!
下课了,班主任 已回到办公室
下课了,王老师 已回到办公室
优点
- 符合开闭原则
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系;
- 目标与观察者之间建立了一套触发机制。
缺点
- 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用;
- 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
应用场景
当更改一个对象的状态可能需要更改其他对象,并且实际的对象集事先未知或动态更改时,请使用观察者模式。
注意事项: 1、JAVA 中已经有了对观察者模式的支持类。 2、避免循环引用。 3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。
源码中的应用
#JDK:
java.util.Observable
#Spring:
org.springframework.context.ApplicationListener
Observable源码分析
Observable接口
public interface Observer {
void update(Observable o, Object arg);
}
Observable类
public class Observable {
private Vector<Observer> obs;
//添加观察者
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);
}
//通知所有观察者
public void notifyObservers() {
notifyObservers(null);
}
public void notifyObservers(Object arg) {
/*
* a temporary array buffer, used as a snapshot of the state of
* current Observers.
*/
Object[] arrLocal;
synchronized (this) {
/* We don't want the Observer doing callbacks into
* arbitrary code while holding its own Monitor.
* The code where we extract each Observable from
* the Vector and store the state of the Observer
* needs synchronization, but notifying observers
* does not (should not). The worst result of any
* potential race-condition here is that:
* 1) a newly-added Observer will miss a
* notification in progress
* 2) a recently unregistered Observer will be
* wrongly notified when it doesn't care
*/
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
}
使用JDK提供的类实现观察者模式
通过使用JDK的类来实现上面实例相关的观察模式,自带的观察者的类有Observer接口和Observable类,使用这两个类中的方法可以很好的完成观察者模式,而且JDK帮我们做了相关的加锁操作,保证了线程安全,整体来说会对我们上面的例子进行改进和简化操作,代码如下:
package com.niuh.designpattern.observer.v2;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Observable;
import java.util.Observer;
/**
* 观察者模式
*/
public class ObserverPattern {
// 步骤6:开始上课,开始记录报告
public static void main(String[] args) {
// 创建学生主体
StudentSubject studentSubject = new StudentSubject();
// 创建观察者老师
TeacherObserver teacherObversable = new TeacherObserver();
// 创建观察者班主任
HeadTeacherObserver headTeacherObserver = new HeadTeacherObserver();
// 学生反映上课状态
studentSubject.setState("讲的不错,很好!");
studentSubject.addObserver(teacherObversable);
studentSubject.addObserver(headTeacherObserver);
// 开始上课
studentSubject.doNotify();
// 上课结束
studentSubject.deleteObserver(headTeacherObserver);
studentSubject.deleteObserver(teacherObversable);
}
}
/**
* 课程类
*/
class Course {
// 上课时间:time
private Date time;
// 上课地点:place
private String place;
// 上课内容:content
private String content;
public Date getTime() {
return time;
}
public void setTime(Date time) {
this.time = time;
}
public String getPlace() {
return place;
}
public void setPlace(String place) {
this.place = place;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Course() {
}
public Course(Date time, String place, String content) {
this.time = time;
this.place = place;
this.content = content;
}
}
/**
* 老师类
* - 观察者之一
* - 观察学生的上课情况
*/
class TeacherObserver implements Observer {
private Course course;
@Override
public void update(Observable o, Object arg) {
DateFormat df = DateFormat.getTimeInstance(DateFormat.LONG, Locale.CHINA);
System.out.println("我是王老师,正在讲课中...");
course = new Course(new Date(), "A栋教学楼", "高等数学");
System.out.println("今天上课时间:" + df.format(course.getTime()) + " 地点:" + course.getPlace() + " 上课内容:" + course.getContent());
}
}
/**
* 班主任来听课
* - 观察者之一
* - 观察学生的上课情况
*/
class HeadTeacherObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("我是班主任来听课了,正在检查课程质量...");
System.out.println("学生反馈课程质量为:" + arg);
}
}
/**
* 学生主体
* - 被观察的对象
*/
class StudentSubject extends Observable {
/**
* 上课状态
*/
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public void doNotify() {
// 设置标志
this.setChanged();
// 通知观察者做出相应动作
this.notifyObservers(state);
}
}
输出结果:
我是班主任来听课了,正在检查课程质量...
学生反馈课程质量为:讲的不错,很好!
我是王老师,正在讲课中...
今天上课时间:上午12时04分27秒 地点:A栋教学楼 上课内容:高等数学
PS:以上代码提交在 Github :https://github.com/Niuh-Study/niuh-designpatterns.git
文章持续更新,可以公众号搜一搜「 一角钱技术 」第一时间阅读。
本文 GitHub org_hejianhui/JavaStudy 已经收录,欢迎 Star。