设计模式实践-观察者模式
什么是观察者模式?什么时候用?
观察者模式常用于GUI。它适用于一对多事件触发的场景,当一个对象的状态被改变,所有依赖它的对象都会得到通知并更新。
场景
- 一对多的事件更新,如控件的点击、长按事件,动画的执行监听等等。
- 跨区域的消息交换,如广播、事件总线等。
怎么实现观察者模式?
经典观察者模式,一般会涉及以下几个对象:
- Subject抽象主题,就是Observable被观察者,抽象为接口,提供增、删观察者
- ConcreteSubject,具体主题,实现Subject接口,将观察者的引用保存到一个集合中,更新时遍历集合通知观察者。
- Observer,抽象观察者,也是一个接口。将更新作为接口方法。
- ConcreteObserver,具体的观察者,实现Observer接口,在更新方法回调时,更新自身。
而我们平时写的接口回调,一般是没有太多抽象接口的,只有以下几个对象:
-
Observer,事件回调接口。提供更新方法。
-
具体事件发出者,被观察者。提供增、删观察者。
其实经典的观察者模式,在JDK中已经提供了Observable和Observer接口,我们直接使用即可。
观察者模式实践,解耦分发聊天室消息
在开发聊天室的过程中,使用WebSocket接收后端发送过来的消息,一般为Json结构,我们就需要解析,解析完成为Java Bean后,再使用Bean进行UI更新。如果直接将解析操作直接写在Activity肯定是很臃肿的(消息种类非常多,将近10种),势必需要抽象去别的类上。而聊天室是分免费和付费的,有一部分消息是他们公用的(一般抽取一个基类),而有一些是个别类型的聊天室才需要使用,那么为了将消息结果解析方和消息结果使用方解耦开来,我们可以使用观察者模式。
实现步骤
- Parser,消息解析方,解析消息Json,并提供消息回调,内部存储回调对象的引用。
- OnReceiveMsgCallback,消息结果使用方,为一个接口,每个监听对象都需要实现。
- 具体的OnReceiveMsgCallback实现类,聊天室有2种类型,再加上公用消息基类监听,一共3个。
最后,OnReceiveMsgCallback接口,提供空实现的实现类,每种类型的聊天室,看业务需要复写具体需要处理的类型回调方法即可。
别废话,上代码~
-
消息注册,3个示例一下(其他类型都是一样套路),接收房间信息、限制发送消息、订单支付消息。一般后端给的Json中,每种消息类型都有一个type来区分消息类型,我们每个消息解析判断本地Json中的type是否是我们这个接收器能处理的类型后再解析即可,解析完成后进行接口回调给外部即可。
- 房间消息,不管免费、付费聊天室都需要。
- 限制发送消息,免费的才需要,5条消息后,老师未回复,则产生限制,无法再发送,除非老师进行回复后,方可解除限制。
- 订单支付消息,付费聊天室使用,用户下单后发送。
public class ConsultingMsgParser {
/**
* 注册房间信息
*/
private void registerRoomInfo(ObservableSubscribeProxy<WebSocketInfo> proxy) {
proxy.subscribe(new WebSocketGenericObserver<WssResponseModel<RoomInfoModel>>() {
@Override
protected boolean isTargetType(String json) {
//判断是否能够解析
return canHandleJson(json, WssCommandTypeEnum.ROOM_INFO);
}
@Override
protected ObservableSource<WssResponseModel<RoomInfoModel>> onParseData(String json) {
//能够解析,则解析并返回解析结果
return parseResponse(json, new TypeToken<WssResponseModel<RoomInfoModel>>() {
}.getType());
}
@Override
protected void onMessage(WssResponseModel<RoomInfoModel> response) {
//通知观察者们,将结果传入
for (OnReceiveMsgCallback callback : mOnReceiveMsgCallbackList) {
callback.onReceiveRoomInfo(response);
}
}
});
}
/**
* 注册免费聊天消息限制消息
*/
private void registerFreeMsgLimit(ObservableSubscribeProxy<WebSocketInfo> proxy) {
proxy.subscribe(new WebSocketGenericObserver<WssResponseModel<SendMsgLimitModel>>() {
@Override
protected boolean isTargetType(String json) {
return canHandleJson(json, WssCommandTypeEnum.SEND_MSG_LIMIT);
}
@Override
protected ObservableSource<WssResponseModel<SendMsgLimitModel>> onParseData(String json) {
return parseResponse(json, new TypeToken<WssResponseModel<SendMsgLimitModel>>() {
}.getType());
}
@Override
protected void onMessage(WssResponseModel<SendMsgLimitModel> response) {
for (OnReceiveMsgCallback callback : mOnReceiveMsgCallbackList) {
if (callback != null) {
if (MsgLimitEnum.LIMIT.getCode() == response.getData().getIsLimit()) {
//限制
callback.onReceiveSendMsgLimitMsg(response);
} else if (MsgLimitEnum.REMOVE_LIMIT.getCode() == response.getData().getIsLimit()) {
//解除限制
callback.onReceiveSendMsgRemoveLimitMsg(response);
}
}
}
}
});
}
/**
* 注册支付高级服务订单消息
*/
private void registerPayServerOrderMsg(ObservableSubscribeProxy<WebSocketInfo> proxy) {
proxy.subscribe(new WebSocketGenericObserver<WssResponseModel<PayServerOrderMsgModel>>() {
@Override
protected boolean isTargetType(String json) {
return isTargetMsg(json, MsgTypeEnum.PAY_SERVER);
}
@Override
protected ObservableSource<WssResponseModel<PayServerOrderMsgModel>> onParseData(String json) {
return parseResponse(json, new TypeToken<WssResponseModel<PayServerOrderMsgModel>>() {
}.getType());
}
@Override
protected void onMessage(WssResponseModel<PayServerOrderMsgModel> response) {
for (OnReceiveMsgCallback callback : mOnReceiveMsgCallbackList) {
callback.onReceivePayServerOrderMsg(response);
}
}
});
}
}
- 回调接口,一系列消息的回调
public interface OnReceiveMsgCallback {
/**
* 接收到房间信息
*/
void onReceiveRoomInfo(WssResponseModel<RoomInfoModel> response);
/**
* 限制发送消息
*/
void onReceiveSendMsgLimitMsg(WssResponseModel<SendMsgLimitModel> response);
/**
* 解除限制,发送消息
*/
void onReceiveSendMsgRemoveLimitMsg(WssResponseModel<SendMsgLimitModel> response);
/**
* 高级服务支付订单消息
*/
void onReceivePayServerOrderMsg(WssResponseModel<PayServerOrderMsgModel> response);
}
//因为有些消息,某种类型的聊天室不需要使用,所以提供接口空实现,具体类型聊天室看业务需要,复写对应类型的回调方法进行处理即可。
public class SimpleOnReceiveMsgCallback implements OnReceiveMsgCallback {
@Override
public void onReceiveRoomInfo(WssResponseModel<RoomInfoModel> response) {
}
@Override
public void onReceiveSendMsgLimitMsg(WssResponseModel<SendMsgLimitModel> response) {
}
@Override
public void onReceiveSendMsgRemoveLimitMsg(WssResponseModel<SendMsgLimitModel> response) {
}
@Override
public void onReceivePayServerOrderMsg(WssResponseModel<PayServerOrderMsgModel> response) {
}
}
- 提供注册接口回调的方法
private ArrayList<OnReceiveMsgCallback> mOnReceiveMsgCallbackList;
/**
* 注册消息接收的回调
*/
public void registerOnReceiveMsgCallback(OnReceiveMsgCallback onReceiveMsgCallback) {
if (mOnReceiveMsgCallbackList == null) {
mOnReceiveMsgCallbackList = new ArrayList<>();
}
mOnReceiveMsgCallbackList.add(onReceiveMsgCallback);
}
-
3个结果处理方进行注册。
- 房间消息,在基类注册进行处理。
ConsultingMsgParser msgParser = new ConsultingMsgParser(); msgParser.registerOnReceiveMsgCallback(new SimpleOnReceiveMsgCallback() { @Override public void onReceiveRoomInfo(WssResponseModel<RoomInfoModel> response) { //收到房间信息,将信息设置到UI... //开始获取消息列表... getMsgList(); } }
- 限制发送消息,免费聊天室进行注册。
msgParser.registerOnReceiveMsgCallback(new SimpleOnReceiveMsgCallback() { @Override public void onReceiveSendMsgLimitMsg(WssResponseModel<SendMsgLimitModel> response) { super.onReceiveSendMsgLimitMsg(response); //设置为限制发送消息状态 mBanTipContext.setState(new LimitSendMsgBanTipState(model)); } @Override public void onReceiveSendMsgRemoveLimitMsg(WssResponseModel<SendMsgLimitModel> response) { super.onReceiveSendMsgRemoveLimitMsg(response); //解除限制发送消息,恢复到普通状态 mBanTipContext.setState(new NormalBanTipState()); } }
- 用户下单消息,付费聊天室进行注册
msgParser.registerOnReceiveMsgCallback(new SimpleOnReceiveMsgCallback() { @Override public void onReceivePayServerOrderMsg(WssResponseModel<PayServerOrderMsgModel> response) { //接收下单成功的系统消息 //插入时间戳条目 insertTimestampItem(response.getDateTime()); //插入系统提示条目 addSystemMsg(new PayServerSuccessTipModel()); //滚动到底部 scrollToBottom(); } }
总结
-
观察者模式的优点:将被观察者和观察者完全隔离,是解耦的。
-
观察者模式的缺点:因为遍历被观察者是同步执行的,如果其中一个观察者回调时处理非常耗时,则影响到下一个遍历的观察者。这种情况观察者接收到消息后应该异步执行,不阻塞消息处理。