什么是观察者模式(Observer)
概念
观察者模式(Observer)又称发布-订阅模式,属于行为型模式,研究对象之间的交互。观察者模式定义了对象间一对多的依赖关系,当被观察者对象发生改变时,所有依赖于它的对象都会得到通知并完成自动更新。观察者模式是为了解决一个对象想知道另一个对象状态变化的问题。
当我们处于一对多情况,比如微信公众号和用户,多个用户订阅了一个公众号,用户想要知道公众号是否更新 ,这时候有两种方式可以获取到公众号的更新,第一种是poll方式,也就是拉取,每个用户都去发送请求,看看公众号是否更新了内容,如果更新了再发出获取最新内容的请求,这样的话,就需要每个用户都定时地高频地请求查看公众号是否有新的内容,给服务器带来了非常大的压力;另外一种方式是push方式,也就是观察者模式,每个用户不需要发送请求去查看公众号是否有新内容,而是等待公众号发布新内容后,公众号再向用户发送请求,说内容已经更新了,快点发个请求过来获取新的内容,最后用户才发送请求去获取新的内容。
还有其他的很多例子,等车大厅,我们不需要一直看着大屏幕,等待广播就可以;煲饭、煮水也是,我们只需要等待叮的一声。
优点
- 制定了观察者和被观察者的行为机制。
- 释放了观察者和被观察者的压力,观察者不需要再去高频率地定时地请求被观察者了。
缺点
- 当观察者对象过多时,通知所有观察者会浪费大量的时间。
- 需要防止观察者和被观察者之间的循环依赖,出现循环依赖会导致系统崩溃。
- 观察者模式只能让观察者知道被观察者的变化,不能知道被观察者是怎么变化的。
原则
“+”代表遵守,“-”代表不遵守或者不相关
原则 | 开放封闭 | 单一职责 | 迪米特 | 里氏替换 | 依赖倒置 | 接口隔离 | 合成复用 |
---|---|---|---|---|---|---|---|
- | + | - | - | + | - | - | |
适用场景
- 对象间存在一对多的关系,一个对象的状态变化会影响其他对象。
- 需要实现类似广播的机制。
如何实现
想要实现观察者模式,需要以下四样东西:
- 主题(Subject)抽象类:提供增加、删除、通知观察者对象的方法,以及定义一个对象来保存观察者。
- 主题类(Concrete Subject):也可以成为被观察者,实现主题抽象类,当主题内部发生改变时通知观察者。
- 观察者接口:定义观察者的行为,需要有一个跟新自己的方法。
- 观察者类:实现观察者接口。
类图
这里的具体目标实现抽象目标,需要实现notifyObserver方法,遍历触发观察者的方法,观察者在抽象目标那就做好了聚合关系,具体观察者实现观察者接口,需要各自实现自己的response方法,完成读取最新状态后的更新。
例子
这里以微信公众号为例子,这里需要公众号(主题对象)、用户(观察者)。
类图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1agAagUA-1633180127364)(C:\Users\xuxiaobai\AppData\Roaming\Typora\typora-user-images\image-20210614141655969.png)]
这个类图有点多,类之间的关系也有点复杂。
IObserver和AbstractSubject呢定义了在观察者模式中需要用到的行为,IWeChatUser和AbstractWeChatSubject分别定义了在微信中用户(观察者)和公众号(主题)应该有的行为。取消了主题对象添加观察者这个操作,而是通过观察者自身去follow一个主题对象,然后再让主题对象增加自身到List中。其他的发布操作就是跟观察者模式规定的一样了。
代码
用户
观察者接口
/**
*
* 观察者接口
* 定义一个观察者应该有的方法
* Created on 2021/6/13.
*
* @author xuxiaobai
*/
public interface IObserver {
/**
* 响应
*/
void update();
}
微信用户接口
/**
*
* 微信用户接口
* 定义用户使用的方法
* Created on 2021/6/14.
*
* @author xuxiaobai
*/
public interface IWeChatUser {
/**
* 关注公众号
* 这里采用用户来关注公众号
* @param subscription
*/
void follow(AbstractWeChatSubject subscription) ;
/**
* 取消关注
*/
void unfollow(AbstractWeChatSubject subscription);
/**
* 获取用户名
* @return
*/
String getUserName();
/**
* 改名
*/
void setUserName(String newName,String password);
}
微信用户
/**
* 观察者
* 用户
* Created on 2021/6/13.
*
* @author xuxiaobai
*/
public class UserObserver implements IObserver,IWeChatUser{
/**
* 添加到那个公众号了,更新时需要使用
* 这里是依赖于抽象
*/
private AbstractWeChatSubject subscription;
//用户名
private String name;
/**
* 密码
* 改密码的功能就不写了
*/
private String password;
public UserObserver(String name,String password){
this.name=name;
this.password=password;
}
@Override
public void update() {
if (this.subscription==null) return;
//触发跟新后,请求公众号获取最新内容
String currentArticle = this.subscription.getCurrentArticle();
System.out.println("<"+this.name);
System.out.println("----"+this.subscription.getName()+"的最新文章----");
System.out.println(currentArticle);
System.out.println(this.name+">");
}
/**
* 取关
* @param subscription
*/
@Override
public void unfollow(AbstractWeChatSubject subscription) {
// if (subscription==this.subscription){
// subscription.remove(this);
// this.subscription=null;
// }
System.out.println("这么好的公众号取关干嘛");
}
/**
* 关注公众号
* @param subscription
* @return
*/
@Override
public void follow(AbstractWeChatSubject subscription) {
this.subscription = subscription;
subscription.add(this);
System.out.println("关注成功!");
}
/**
* 获取当前用户名称
* @return
*/
public String getUserName() {
return this.name;
}
@Override
public void setUserName(String newName, String password) {
System.out.println("<"+this.name);
System.out.println("修改名称。。");
try {
if (this.password.equals(password)){
this.name=newName;
System.out.println("修改成功!");
return;
}
System.out.println("密码错误!");
}finally {
System.out.println(this.name+">");
}
}
}
主题/公众号
主题抽象类
/**
* 微信公众号抽象类
* 所有微信公众号都要实现这个抽象类
* Created on 2021/6/14.
*
* @author xuxiaobai
*/
public abstract class AbstractWeChatSubject extends AbstractSubject{
//当前微信公众号名称
protected String name;
/**
* 当前最新文章,可以当作本类的状态
*
* 可以考虑再增加一个list把过往的文章都保存下来,
* private List<String> articles=new ArrayList<>();
* 然后增加一个getHistory的方法,供用户调用,可以查看过往的文章
* 我这里就不作演示了
*/
protected String currentArticle;
public AbstractWeChatSubject(String name){
this.name=name;
System.out.println("注册公众号,名称为"+name);
}
/**
* 获取最新文章
* @return
*/
abstract String getCurrentArticle();
/**
* 获取公众号名称
* @return
*/
abstract String getName();
/**
* 发布文章
* @param article
*/
abstract void updateArticle(String article);
}
微信主题抽象类
/**
* 微信公众号抽象类
* 所有微信公众号都要实现这个抽象类
* Created on 2021/6/14.
*
* @author xuxiaobai
*/
public abstract class AbstractWeChatSubject extends AbstractSubject{
//当前微信公众号名称
protected String name;
public AbstractWeChatSubject(String name){
this.name=name;
System.out.println("注册公众号,名称为"+name);
}
/**
* 获取最新文章
* @return
*/
abstract String getCurrentArticle();
/**
* 获取公众号名称
* @return
*/
abstract String getName();
/**
* 发布文章
* @param article
*/
abstract void updateArticle(String article);
}
微信公众号
/**
* 主题类
* 微信公众号
* Created on 2021/6/13.
*
* @author xuxiaobai
*/
public class WeChatSubscription extends AbstractWeChatSubject{
//是否有变化标志
private boolean isChange = false;
public WeChatSubscription(String name){
super(name);
}
/**
* 跟新文章
* @param article
*/
@Override
public void updateArticle(String article){
super.currentArticle=article;
this.isChange=true;
System.out.println("当前公众号("+this.name+")更新文章");
//跟新完成后推送
notifyObserver();
}
/**
* 获取最新文章
* @return
*/
@Override
public String getCurrentArticle(){
return super.currentArticle;
}
@Override
public void notifyObserver() {
//可以开启一个线程来处理
if (this.isChange) {
System.out.println("开始推送。。。。");
this.isChange=false;
//当改变时才通知观察者,推送文章
observers.forEach((item) -> {
item.update();
});
System.out.println("-----推送完成-----");
return;
}
System.out.println("未检测到新文章,请重试!");
}
@Override
public void additionalOperationsInAdd(IObserver observer) {
/**
* 转个型后调用获取名称的方法
*/
System.out.println("新增粉丝:"+((UserObserver) observer).getUserName());
}
@Override
public String getName(){
return this.name;
}
}
测试
/**
* Created on 2021/6/13.
*
* @author xuxiaobai
*/
public class ObserverTest {
public static void main(String[] args) {
AbstractWeChatSubject subject=new WeChatSubscription("程序员徐小白");
IWeChatUser user1=new UserObserver("小明同学","123");
IWeChatUser user2=new UserObserver("小强同学","123");
user1.setUserName("小明","123");
user1.follow(subject);
subject.updateArticle("感觉大家的关注!");
user2.follow(subject);
subject.updateArticle("第二篇文章");
/**
* 结果:
* 注册公众号,名称为程序员徐小白
* <小明同学
* 修改名称。。
* 修改成功!
* 小明>
* 新增粉丝:小明
* 关注成功!
* 当前公众号(程序员徐小白)更新文章
* 开始推送。。。。
* <小明
* ----程序员徐小白的最新文章----
* 感觉大家的关注!
* 小明>
* -----推送完成-----
* 新增粉丝:小强同学
* 关注成功!
* 当前公众号(程序员徐小白)更新文章
* 开始推送。。。。
* <小明
* ----程序员徐小白的最新文章----
* 第二篇文章
* 小明>
* <小强同学
* ----程序员徐小白的最新文章----
* 第二篇文章
* 小强同学>
* -----推送完成-----
*
*/
}
}
差不多三百多行的代码了,看到这了还不给点个赞吗?
这里的代码还有待补充,这里只是用来演示观察者模式,如果你有兴趣的话可以拷贝然后自己做一些补充。
总结
在Java中也有一个观察者接口:java.util.Observer,直接使用这个接口也是可以的,不过自己写的话会比较合适自己的需求。
个人感觉观察者模式和中介者模式有点相似,都是把接收消息的对象注册到一个中间对象上,这个中间对象接收到消息后,再转发给接收消息的对象,只不过中介者模式是处理对象多对多的关系,而观察者模式是处理对象一对多的关系。
在使用观察者模式时,需要注意比秒观察者和被观察者的循环依赖,会导致系统卡死,还有就是被观察者通知观察者这个操作可以采用异步的方式来处理,否者遍历所有观察者会让被观察者卡在这一步。