一文看懂观察者模式及案例分析
一、基本介绍
观察者模式是一种对象行为模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。在观察者模式中,主题是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅并接收通知。观察者模式不仅被广泛应用于软件界面元素之间的交互,在业务对象之间的交互、权限管理等方面也有广泛的应用。
—— 引用自百度百科
二、模式的定义与特点
观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
- 目标与观察者之间建立了一套触发机制。
它的主要缺点如下:
- 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
- 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
三、模式的结构与实现
实现观察者模式时要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则。
观察者模式的主要角色如下:
四、具体实现步骤
相关场景描述:
某天的下午,班主任通知某班学生和老师将要听一节课,以此来对老师的授课质量进行评分。老师和学生收到后开始安排相关的课程。上课期间老师和班主任通过观察学生的神情来预判课程的讲的好坏,老师观察到学生皱眉头可以适当调节课程气氛,班主任观察到学生课堂氛围好转,给与高分。课程结束后,班主任和老师回到办公室,一起回顾这节开心的课程。
①使用自定义的方式实现观察者模式
1.构建一个课程实体类(以下均省略Setter和Getter)
/**
* 课程类
* - 上课时间:time
* - 上课地点:place
* - 上课内容:content
*/
public class Course {
private Date time;
private String place;
private String content;
public Course() {
}
public Course(Date time, String place, String content) {
this.time = time;
this.place = place;
this.content = content;
}
}
2.构建一个发现者的抽象类以及相关的实现类,老师和班主任都分别继承了该接口并重写相关方法,
public abstract class Observer {
abstract void update(Object args);
public Observer(String identity) {
this.identity = identity;
}
private String identity;
}
老师拿着教材开始来上课:
/**
* 老师类
* - 观察者之一
* - 观察学生的上课情况
*/
public 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栋教学楼", "Java课程");
System.out.println("今天上课时间:"+df.format(course.getTime()) + " 地点:" + course.getPlace() + " 上课内容:" + course.getContent());
}
public TeacherObserver(String identity) {
super(identity);
}
}
班主任来听课:
/**
* 班主任来听课
* - 观察者之一
* - 观察学生的上课情况
*/
public class HeadTeacherObserver extends Observer {
@Override
public void update(Object args) {
System.out.println("我是班主任来听课了,正在检查课程质量...");
System.out.println("学生反馈课程质量为:" + args);
}
public HeadTeacherObserver(String identity) {
super(identity);
}
}
这时候轮到我们学生的主体登场了:
/**
* 主体类
* - 模拟被观察者主体
*/
public abstract class Subject {
/**
* 修改通知
*/
abstract void doNotify();
/**
* 添加被观察者
*/
abstract void addObservable(Observer o);
/**
* 移除被观察者
*/
abstract void removeObservable(Observer o);
}
学生主体,被观察的对象:
/**
* 学生主体
* - 被观察的对象
*/
public class StudentSubject extends Subject {
/**
* 上课状态
*/
private String 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();
}
}
}
那么我们焦急万分等待课程终于如期进行了,开始记录报告吧:
/**
* 课程报告
* - 一起回顾这节有意思的课
*/
public class CourseReport {
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);
}
}
回到办公室的老师和班主任都眉开眼笑,学生对这节课也是很满意,可以看到如下的报告回顾:
我是老师,正在讲课中...
今天上课时间:下午03时00分00秒 地点:A栋教学楼 上课内容:Java课程
我是班主任来听课了,正在检查课程质量...
学生反馈课程质量为:(*^▽^*)讲的不错,很好,随手点个关注和在看!
下课了,班主任 已回到办公室
下课了,老师 已回到办公室
②使用JDK提供的类实现观察者模式
这里我们可以使用JDK的类来实现相关的观察模式,自带的观察者的类有Observer接口和Observable类,使用这两个类中的方法可以很好的完成观察者模式,而且JDK帮我们做了相关的加锁操作,保证了线程安全,整体来说会对我们上面的类进行改进和简化操作。
我们主要需要改造的是被观察的主体和实现观察者的类,如下所示:
/**
* 老师类
* - 观察者之一
* - 观察学生的上课情况
*/
public 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栋教学楼", "Java课程");
System.out.println("今天上课时间:"+df.format(course.getTime()) + " 地点:" + course.getPlace() + " 上课内容:" + course.getContent());
}
}
/*************************************/
/**
* 班主任来听课
* - 观察者之一
* - 观察学生的上课情况
*/
public class HeadTeacherObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("我是班主任来听课了,正在检查课程质量...");
System.out.println("学生反馈课程质量为:" + arg);
}
}
/*************************************/
/**
* 学生主体
* - 被观察的对象
*/
public class StudentObservable extends Observable {
/**
* 上课状态
*/
private String state;
public void doNotify() {
// 设置标志
this.setChanged();
// 通知观察者做出相应动作
this.notifyObservers(state);
}
}
/*************************************/
public class CourseReport {
public static void main(String[] args) {
// 创建学生主体
StudentObservable studentObservable = new StudentObservable();
// 创建观察者老师
TeacherObserver teacherObversable = new TeacherObserver();
// 创建观察者班主任
HeadTeacherObserver headTeacherObserver = new HeadTeacherObserver();
// 学生反映上课状态
studentObservable.setState("(*^▽^*)讲的不错,很好,随手点个关注和在看!");
studentObservable.addObserver(teacherObversable);
studentObservable.addObserver(headTeacherObserver);
// 开始上课
studentObservable.doNotify();
// 上课结束
studentObservable.deleteObserver(headTeacherObserver);
studentObservable.deleteObserver(teacherObversable);
}
}
运行效果如下:
我是老师,正在讲课中...
今天上课时间:下午03时00分00秒 地点:A栋教学楼 上课内容:Java课程
我是班主任来听课了,正在检查课程质量...
学生反馈课程质量为:(*^▽^*)讲的不错,很好,随手点个关注和在看!
划重点:
这里Observer内部只定义了一个方法,主要用于在被观察的对象发出通知后做出相应的动作:
update(Observable o, Object arg);
Observable类中的方法有:
addObserver(添加观察者)、deleteObserver(删除观察者)、notifyObservers(唤醒所有的观察者,可带入参数)、deleteObservers(删除所有观察者)、setChanged(改变标志为True)、clearChanged(改变标志为false)、hasChanged(查看标志状态)、countObservers(统计观察者个数)
这些方法中有些加了同步机制保证线程安全,我们可以根据需要使用提供的已有相关方法,增强代码的复用性。有兴趣的可以看下源码的实现,这里就不再累述。
五、总结
观察者模式的让我们知道了在设计开发的时候一定要“多用组合,少用继承”。
我们设计开发是应该是针对接口变成,而不针对实现编程。
这是一种创建松散耦合代码的技术。它定义对象间 一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。由主体和观察者组成,主体负责发布事件,同时观察者通过订阅这些事件来观察该主体。主体并不知道观察者的任何事情,观察者知道主体并能注册事件的回调函数。