一、观察者模式定义
观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。
二、观察者模式的结构和说明
- Subject 被观察者(目标对象)
它管理着观察它的观察者,提供添加和删除观察者的接口,并在自身有变化时,通知所有添加的观察者。 - Observer 观察者
观察者可以接收被观察者发送过来的消息。 - ConcreteSubject 具体的被观察者
定义被观察者具体的业务逻辑,同时定义对哪些事件进行通知。 - ConcreteObserver 具体的观察者
定义具体的接收到消息后的处理逻辑。
三、观察者模式示例
假设这么一个场景:博主开设了一个设计模式专栏,当博主专栏里发布新的博客时,订阅该专栏的用户可以收到通知。
Subject 被观察者(目标对象)
/**
* 被观察者(目标对象)
* 它管理着观察它的观察者,提供添加和删除观察者的接口,并在自身有变化时,通知所有添加的观察者。
*/
public class Subject {
/**
* 用来保存添加的观察者对象
*/
private List<Observer> observers = new ArrayList<>();
/**
* 添加观察者对象
* @param observer 观察者对象
*/
public void addObserver(Observer observer){
observers.add(observer);
}
/**
* 删除观察者对象
* @param observer 观察者对象
*/
public void deleteObserver(Observer observer){
observers.remove(observer);
}
/**
* 通知所有观察者
* @param content
*/
protected void notifyObservers(String content){
for(Observer observer : observers){
observer.update(this, content);
}
}
}
Observer 观察者
/**
* 观察者
* 观察者可以接收被观察者发送过来的消息。
*/
public interface Observer {
/**
* 接收通知的方法
* @param subject 具体的被观察对象,可以通过拉的方式获取内容
* @param content (直接推送过来的)新内容
*/
void update(Subject subject, String content);
}
BlogSubject 博客专栏(具体的被观察者)
/**
* 博客专栏
*/
public class BlogSubject extends Subject{
/**
* 专栏名称
*/
private String name;
/**
* 存放专栏里的博客
*/
private List<String> articles;
public BlogSubject(String name) {
this.name = name;
}
public String getName() {
return name;
}
public List<String> getArticles() {
return articles;
}
/**
* 发布新博客
* @param article
*/
public void addArticle(String article) {
if(this.articles == null){
this.articles = new ArrayList<>();
}
this.articles.add(0,article);
// 有新博客了,通知关注者
this.notifyObservers(article);
}
}
CsdnUserObserver CSDN用户(具体的观察者)
/**
* CSDN用户
*/
public class CsdnUserObserver implements Observer{
/**
* 用户昵称
*/
private String userName;
public CsdnUserObserver(String userName) {
this.userName = userName;
}
/**
* 接收消息
* @param subject 具体的被观察对象,可以通过拉的方式获取内容
* @param content (直接推送过来的)新内容
*/
@Override
public void update(Subject subject, String content) {
// 输出推送过来的信息
System.out.println(this.userName+" 阅读了:"+content);
// 去拉取最新信息
System.out.println(this.userName+" 阅读了:"+
((BlogSubject)subject).getName()+" -> "+((BlogSubject) subject).getArticles().get(0));
}
}
以上,基本观察者模式的结构就已经设计好了,下边我们来模拟一下使用。
public class Client {
public static void main(String[] args) {
//创建一个专栏,作为具体的被观察者
Subject subject = new BlogSubject("设计模式专栏");
// 创建三个观察者
CsdnUserObserver observer1 = new CsdnUserObserver("张无忌");
CsdnUserObserver observer2 = new CsdnUserObserver("令狐冲");
CsdnUserObserver observer3 = new CsdnUserObserver("萧峰");
// 添加观察者
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.addObserver(observer3);
//博主发布新博客了
((BlogSubject) subject).addArticle("Java设计模式及应用场景之《观察者模式》");
}
}
执行main方法后,得到下边的输出:
张无忌 阅读了:Java设计模式及应用场景之《观察者模式》
张无忌 阅读了:设计模式专栏 -> Java设计模式及应用场景之《观察者模式》
令狐冲 阅读了:Java设计模式及应用场景之《观察者模式》
令狐冲 阅读了:设计模式专栏 -> Java设计模式及应用场景之《观察者模式》
萧峰 阅读了:Java设计模式及应用场景之《观察者模式》
萧峰 阅读了:设计模式专栏 -> Java设计模式及应用场景之《观察者模式》
四、用Java自带的功能实现观察者模式
观察者模式是Java非常重要的一个设计模式。对于观察者模式,JDK已经为我们提供了对应的接口和类。分别是:java.util.Observable
(被观察者);java.util.Observer
(观察者)。
下面我们用Java提供的功能来实现观察者模式示例。
BlogSubject 博客专栏(具体的被观察者)
/**
* 博客专栏
*/
public class BlogSubject extends Observable {
/**
* 专栏名称
*/
private String name;
/**
* 存放专栏里的博客
*/
private List<String> articles;
public BlogSubject(String name) {
this.name = name;
}
public String getName() {
return name;
}
public List<String> getArticles() {
return articles;
}
/**
* 发布新博客
* @param article
*/
public void addArticle(String article) {
if(this.articles == null){
this.articles = new ArrayList<>();
}
this.articles.add(0,article);
//注意在用Java中的Observer模式的时候,这句话不可少
this.setChanged();
// 有新博客了,通知关注者
this.notifyObservers(article);
// 如果单纯使用拉的方式,可以用下边这个方法
// this.notifyObservers();
}
}
CsdnUserObserver CSDN用户(具体的观察者)
/**
* CSDN用户
*/
public class CsdnUserObserver implements Observer{
/**
* 用户昵称
*/
private String userName;
public CsdnUserObserver(String userName) {
this.userName = userName;
}
/**
* 接收消息
* @param o 具体的被观察对象,可以通过拉的方式获取内容
* @param arg (直接推送过来的)新内容
*/
@Override
public void update(Observable o, Object arg) {
// 输出推送过来的信息
System.out.println(this.userName+" 阅读了:"+arg.toString());
// 去拉取最新信息
System.out.println(this.userName+" 阅读了:"+
((BlogSubject)o).getName()+" -> "+((BlogSubject) o).getArticles().get(0));
}
}
以上,观察者模式的结构就已经重新设计好了,下边我们再来模拟一下使用。
public class Client {
public static void main(String[] args) {
//创建一个专栏,作为具体的被观察者
Observable observable = new BlogSubject("设计模式专栏");
// 创建三个观察者
CsdnUserObserver observer1 = new CsdnUserObserver("张无忌");
CsdnUserObserver observer2 = new CsdnUserObserver("令狐冲");
CsdnUserObserver observer3 = new CsdnUserObserver("萧峰");
// 添加观察者
observable.addObserver(observer1);
observable.addObserver(observer2);
observable.addObserver(observer3);
//博主发布新博客了
((BlogSubject) observable).addArticle("Java设计模式及应用场景之《观察者模式》");
}
}
执行main方法后,得到同样的输出:
萧峰 阅读了:Java设计模式及应用场景之《观察者模式》
萧峰 阅读了:设计模式专栏 -> Java设计模式及应用场景之《观察者模式》
令狐冲 阅读了:Java设计模式及应用场景之《观察者模式》
令狐冲 阅读了:设计模式专栏 -> Java设计模式及应用场景之《观察者模式》
张无忌 阅读了:Java设计模式及应用场景之《观察者模式》
张无忌 阅读了:设计模式专栏 -> Java设计模式及应用场景之《观察者模式》
用Java自带的功能实现观察者模式,相较于完全自己实现,有以下一些改变:
- 不需要自己定义观察者和被观察者接口。
- 触发通知时,需要先调用
setChanged()
方法,这个是Java为了实现更精确的触发控制而提供的功能。 - Java提供的功能做了线程安全的处理(具体实现可以看源码)。
五、观察者模式的优缺点
优点:
- Subject(被观察者)和Observer(观察者)之间是松耦合的,分别可以各自独立改变。
- 观察者模式实现了动态联动,支持广播通信(被观察者会向所有的登记过的观察者发出通知)。
缺点:
- 如果一个Subject被大量Observer订阅的话,在广播通知的时候可能会有效率问题。并且,如果是顺序执行,中间某个环节卡壳,会影响到后续流程的执行(针对这个缺点,可以考虑采用异步的方式处理)。
六、观察者模式的应用场景
-
对一个对象状态的更新,需要其他对象同步更新,而且其他对象的数量动态可变。
如,用户支付成功一个订单时,会触发这些操作:- 记录文本日志
- 发送短信消息
- 赠送活动优惠券
并且以后还会扩展一些其它的操作,这时比较适合用观察者模式。
-
对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节。
-
Swing中的事件处理(Swing组件是被观察者,每个实现监听器的类就是观察者)。
-
Spring ApplicationContext 事件机制中的观察者模式。