观察者模式(Observer Pattern)
文章目录
一、相关概念
定义
当对象存在一对多关系时,则使用观察者模式。当一个对象被修改时,则自动通知依赖它的对象,但要考虑到易用和低耦合。例如:股票价格和股民
观察者模式属于行为性模式
别名:发布-订阅模式,后面‘订阅-发布模式’被优化多了一个处于观察者与被观察者之间的中转站,实现了解耦。
优点
- 观察者与被观察者是抽象耦合
- 建立了一套触发机制
- 满足开闭原则,增加新的 观察者不需要修改原先代码
缺点
-
如果一个被观察者对象具有多个直接或者间接的观察者 ,通知所有的观察者会耗费较多时间
-
观察者与被观察者直接有循环依赖,会导致系统崩溃(死循环)
目标
解除系统耦合性,不用在维护对象的一致性中而使系统紧密耦合。
结构
1.抽象主题(Sbuject):
也叫抽象目标类,提供了一个用于保存观察者对象的聚集类。里面提供了对应的增加(addObserver)、删除(deleteObserver)和通知所有观察者对象(notifyObservers)的方法。
2.具体主题(Concrete Sbuject):
也叫具体目标类,它实现抽象目标中的通知方法,也当具体主题的内部发生变化时,通知所有注册过的观察者对象
3.抽象观察者(Observer):
它是一个抽象类,包含一个更新自己的抽象方法,当接到具体主题的更改通知时被调用
4.具体观察者(Concrete Observer):
它实现抽象观察者中的更新方法,当接到具体主题的更改通知时,给观察者对象做出相应的处理,如发送消息。
二、代码实现流程
1.建立观察者与被观察者直接的关系
/***
* Web Scoket服务.
* <li>观察者模式
*/
@Component
@ServerEndpoint(value = "/websocket/{principal}")
public class WebSocketEndpoint {
/**
* Web Socket连接建立成功的回调方法
* @param principal 通过该字符串找到不同的主题
* @param session 观察者的session
*/
@OnOpen
public void onOpen(@PathParam("principal") String principal, Session session) {
// create observer 创建 观察者(观众) --- 拿到观察者的sesion消息,后面通过session进行消息发送
WebSocketObserver observer = new WebSocketObserver(session);
// get subject 创建 被观察者(演员)
WebSocketSubject subject = WebSocketSubject.Holder.getSubject(principal);//①
// register observer into subject 被观察者(演员)登记观察者(观众)
subject.addObserver(observer);//②
}
①方法,创建或复用 Web Socket主题
/***
* Web Socket 主题(被观察者)
*/
public class WebSocketSubject extends Observable {
/**
* subject键值
*/
private String principal;
private WebSocketSubject(String principal) {
this.principal = principal;
}
/**
* 通过不同字符(principal)来区分多个WebSocket主题
* 不同主题下会有不同的观察者
*/
public static class Holder {
private static ConcurrentHashMap<String, WebSocketSubject> subjects = new ConcurrentHashMap<>();
public static WebSocketSubject getSubject(String principal) {
//判断是否存在该主题,有就复用,没有就新建
if (subjects.containsKey(principal)) {
return subjects.get(principal);
}
WebSocketSubject subject = new WebSocketSubject(principal);
subjects.put(principal, subject);
return subject;
}
}
}
② 建立观察者和被观察者的关系----将该主题下的被观察者存入集合中
public class Observable {
//被登记的观察者(观众)都存在该集合
private Vector<Observer> obs;
/** Construct an Observable with zero Observers. */
public Observable() {
obs = new Vector<>();
}
/**
* Adds an observer to the set of observers for this object, provided
* that it is not the same as some observer already in the set.
* The order in which notifications will be delivered to multiple
* observers is not specified. See the class comment.
*
* @param o an observer to be added.
* @throws NullPointerException if the parameter o is null.
*/
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
}
2.当被观察者(演员)发生变化,通知观察者
public class WebSocketNoticeLZ {
/**
* 被观察者(演员)发生了变化,通知观察者(观众)
* @param principal 通过该字符串找到不同的主题
*/
public static void notice(Object object,String principal) {
String type = "notice";
JSONObject data = JSONObject.parseObject(JSONObject.toJSONString(object));
//得到指定的主题
WebSocketSubject subject = WebSocketSubject.Holder.getSubject(principal);
//通知该主题下的所有观察者(观众)
subject.notify(type, data.toJSONString());
}
}
/***
* Web Socket 具体主题(被观察者)
*/
public class WebSocketSubject extends Observable {
/**
* 通知观察者(观众)
* 被观察者(演员)发生了变化,观察者(观众)也要做出相应变化
* @param type
* @param data
*/
public void notify(String type, String data) {
//被观察者对象状态发生了变化
super.setChanged();
JSONObject json = new JSONObject();
json.put("type", type);
json.put("data", data);
//调用所有登记过的观察者的update方法
super.notifyObservers(json.toJSONString());
}
public class Observable {
private boolean changed = false;
//被登记的观察者(观众)都存在该集合
private Vector<Observer> obs;
/**
* Marks this <tt>Observable</tt> object as having been changed; the
* <tt>hasChanged</tt> method will now return <tt>true</tt>.
*/
protected synchronized void setChanged() {
changed = true;
}
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--)
//遍历所有观察者的update方法,进行发送消息
((Observer)arrLocal[i]).update(this, arg);
}
3.通过观察者的session进行通知
/***
* Web Socket 观察者.(具体观察者)
*/
public class WebSocketObserver implements Observer {
/**
* Web Socket session
*/
private Session session;
public WebSocketObserver(Session session) {
this.session = session;
}
public Session getSession() {
return session;
}
@Override
public void update(Observable o, Object arg) {
String message = (String) arg;
try {
if (session.isOpen()) {
//发送消息
session.getBasicRemote().sendText(message);
}
} catch (IOException e) {
throw new RuntimeException("websocket sendText erro.", e);
}
}
}
三 、源码内容
//前端请求建立连接
@Component
@ServerEndpoint(value = "/websocket/{principal}")
public class WebSocketEndpoint {
/**
* Web Socket连接建立成功的回调方法
* @param principal 通过该字符串找到不同的主题
* @param session 观察者的session
*/
@OnOpen
public void onOpen(@PathParam("principal") String principal, Session session) {
// create observer 观察者 --- 拿到观察者的sesion消息,后面通过session进行消息发送
WebSocketObserver observer = new WebSocketObserver(session);
// get subject 被观察者(演员)
WebSocketSubject subject = WebSocketSubject.Holder.getSubject(principal);
// register observer into subject 被观察者(演员)登记观察者(观众)
subject.addObserver(observer);
}
public class WebSocketNoticeLZ {
/**
* 服务端向客户端推送消息 hint true 提示框
* @param principal 通过该字符串找到不同的主题
*/
public static void notice(Object object,String principal) {
String type = "notice";
JSONObject data = JSONObject.parseObject(JSONObject.toJSONString(object));
//通知被登记的观察者(观众)
WebSocketSubject subject = WebSocketSubject.Holder.getSubject(principal);
subject.notify(type, data.toJSONString());
}
}
/***
* Web Socket 主题(被观察者)
*/
public class WebSocketSubject extends Observable {
/**
* subject键值
*/
private String principal;
private WebSocketSubject(String principal) {
this.principal = principal;
}
public String getPrincipal() {
return principal;
}
/**
* 通知观察者(观众)
* 被观察者(演员)发生了变化,观察者(观众)也要做出相应变化
* @param type
* @param data
*/
public void notify(String type, String data) {
//被观察者对象状态发生了变化
super.setChanged();
JSONObject json = new JSONObject();
json.put("type", type);
json.put("data", data);
//调用所有登记过的观察者的update方法
super.notifyObservers(json.toJSONString());
}
/**
* 通过不同字符(principal)来区分多个WebSocket主题
* 不同主题下会有不同的观察者
*/
public static class Holder {
private static ConcurrentHashMap<String, WebSocketSubject> subjects = new ConcurrentHashMap<>();
public static WebSocketSubject getSubject(String principal) {
if (subjects.containsKey(principal)) {
return subjects.get(principal);
}
WebSocketSubject subject = new WebSocketSubject(principal);
subjects.put(principal, subject);
return subject;
}
}
}
/***
* Web Socket 观察者.</br>
*
* @attation 重写equals方法{@link #equals(Object)}
*
*/
public class WebSocketObserver implements Observer {
/**
* Web Socket session
*/
private Session session;
public WebSocketObserver(Session session) {
this.session = session;
}
public Session getSession() {
return session;
}
@Override
public void update(Observable o, Object arg) {
String message = (String) arg;
try {
if (session.isOpen()) {
//发送消息
session.getBasicRemote().sendText(message);
}
} catch (IOException e) {
throw new RuntimeException("websocket sendText erro.", e);
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((session == null) ? 0 : session.getId().hashCode());
return result;
}
}