【设计模式】结构型-①观察者模式
【设计模式】结构型-①观察者模式
注
学习核心
- 观察者模式核心
- 概念:一个行为发生时传递信息,然后由另一个用户接收并做出相应处理。支持动态添加、移除监听事件(接收者可以动态订阅、取订事件)
- 组成:事件监听(监听者)、事件处理(监听管理器)
- 事件监听:EventListener(接口定义)和对应的监听实现类xxxEventListener
- 事件处理:EventManager事件管理类定义,提供添加事件监听、移除事件监听、执行事件三个方法
- 应用场景分析
【MQ】消息服务场景:行为发生->传递消息->消息处理,这也是一种基于观察者模式的设计思路
【事件监听】
- 事件监听总线:一些业务场景中为了提升应用性能,会将主线服务和其他业务服务进行分离,为了降低两者间的耦合度,会采用观察者模式进行处理
- JAVA的Swing、GUI的组件事件监听:Listener机制(例如GUI、Swing按钮等事件监听)
- Redis的哨兵模式更新主节点的通知机制:Redis中哨兵模式下更新主节点的消息通过发布订阅模式进行消息传递以更新关系,也可以理解为从节点订阅了相关信息,当监听到有相应事件发生时做出相应的操作
【小汽车摇号】场景:摇号业务通知场景(摇号(主业务)+ 通知(MQ、Message 辅线业务))
- 原始实现流程:模拟摇号(核心主链路)=》辅助链路:模拟发短信、模拟发MQ消息等 =》返回摇号结果
- 观察者模式优化:将主、辅进行分离,通过监听/观察主线路事件(模拟摇号)触发辅线事件(通知结果)发生
概念说明
观察者模式
当一个⾏为发⽣时传递信息给另外⼀个⽤户接收做出相应的处理,两者之间没有直接的耦合关联。
除了⽣活中的场景外,在我们编程开发中也会常⽤到⼀些观察者的模式或者组件,例如MQ服务,虽然MQ服务是有⼀个通知中⼼并不是每⼀个类服务进⾏通知,但整体上也可以算作是观察者模式的思路设计。再⽐如类似事件监听总线,让主线服务与其他辅线业务服务分离,为了使系统降低耦合和增强扩展性,也会使⽤观察者模式进⾏处理。(例如Java Swing、GUI的事件监听,也可以理解为一个观察者模式)
场景案例
案例1:小汽车摇号
场景介绍
小汽车摇号,摇号通知的场景,如果开发摇号功能,一般情况下则考虑类似借助百度或者其他插件发送短信通知,并需要对外部的用户做一些事件通知或额外的一些辅助流程。
最简单的实现方式就是类似流程式地直接迭代到类中实现,但实际上要区分主线任务和辅助功能概念,例如完成某个行为之后需要触发MQ传递消息给到外部或者PUSH消息给到用户,这些辅助操作并不属于核心流程链路,可以考虑通过事件通知的方式进行处理
🧨原生实现
基础代码
MinibusTargetService(摇号接口:模拟摇号)、LotteryResult(摇号结果)、LotteryService(摇号服务接口)、LotteryServiceImpl(摇号服务接口实现)
原始实现流程:模拟摇号(核心主链路)=》辅助链路:模拟发短信、模拟发MQ消息等 =》返回摇号结果
public class LotteryServiceImpl implements LotteryService {
private Logger logger = LoggerFactory.getLogger(LotteryServiceImpl.class);
private MinibusTargetService minibusTargetService = new MinibusTargetService();
@Override
public LotteryResult doDraw(String uId) {
// 摇号
String lottery = minibusTargetService.lottery(uId);
// 模拟发短信
logger.info("给用户 {} 发送短信通知(短信):{}", uId, lottery);
// 模拟发MQ消息
logger.info("记录用户 {} 摇号结果(MQ):{}", uId, lottery);
// 返回结果
return new LotteryResult(uId, lottery, new Date());
}
}
// 测试接口
public class ModTest {
private Logger logger = LoggerFactory.getLogger(ModTest.class);
@Test
public void test() {
LotteryService lotteryService = new LotteryServiceImpl();
LotteryResult result = lotteryService.doDraw("2765789109876");
logger.info("测试结果:{}", JSON.toJSONString(result));
}
}
除了模拟摇号功能(核心主链路),后面其他的模拟通知都是辅助功能(非核心主链路功能),会随着后续的业务需求发展而扩展,如果基于现有这种开发模式,会导致后期代码难以维护
✨观察者模式
观察者模式模型
如上图所示拆分三大核心:事件监听 、 事件处理 、 具体的业务流程 ,另外在业务流程中LotteryService 定义的是抽象类,因为这样可以通过抽象类将事件功能屏蔽,外部业务流程开
发者不需要知道具体的通知操作。
右下⻆圆圈图表示的是核⼼流程与⾮核⼼流程的结构,⼀般在开发中会把主线流程开发完成后,再使⽤通知的⽅式处理辅助流程。他们可以是异步的,在MQ以及定时任务的处理下,保证最终⼀致性
代码实现
- 事件监听:事件监听定义EventListener、对应两个监听事件的实现MessageEventListener、MQEventListener
- 事件处理:EventManager事件管理类定义
- 具体的业务流程:业务接口定义LotteryService、业务接口实现LotteryServiceImpl
- 测试接口代码
// 事件监听接口定义
public interface EventListener {
void doEvent(LotteryResult result);
}
// 事件监听接口实现(发送短信、发送MQ消息)
public class MessageEventListener implements EventListener {
@Override
public void doEvent(LotteryResult result) {}
}
public class MQEventListener implements EventListener {
@Override
public void doEvent(LotteryResult result) {}
}
// 事件处理定义
public class EventManager {
Map<Enum<EventType>, List<EventListener>> listeners = new HashMap<>();
public EventManager(Enum<EventType>... operations) {
for (Enum<EventType> operation : operations) {
this.listeners.put(operation, new ArrayList<>());
}
}
public enum EventType {
MQ, Message
}
/**
* 订阅
* @param eventType 事件类型
* @param listener 监听
*/
public void subscribe(Enum<EventType> eventType, EventListener listener) {
List<EventListener> users = listeners.get(eventType);
users.add(listener);
}
/**
* 取消订阅
* @param eventType 事件类型
* @param listener 监听
*/
public void unsubscribe(Enum<EventType> eventType, EventListener listener) {
List<EventListener> users = listeners.get(eventType);
users.remove(listener);
}
/**
* 通知
* @param eventType 事件类型
* @param result 结果
*/
public void notify(Enum<EventType> eventType, LotteryResult result) {
List<EventListener> users = listeners.get(eventType);
for (EventListener listener : users) {
listener.doEvent(result);
}
}
}
// 业务接口定义和实现
public abstract class LotteryService {
private EventManager eventManager;
public LotteryService() {
eventManager = new EventManager(EventManager.EventType.MQ, EventManager.EventType.Message);
eventManager.subscribe(EventManager.EventType.MQ, new MQEventListener());
eventManager.subscribe(EventManager.EventType.Message, new MessageEventListener());
}
public LotteryResult draw(String uId) {
LotteryResult lotteryResult = doDraw(uId);
// 需要什么通知就给调用什么方法
eventManager.notify(EventManager.EventType.MQ, lotteryResult);
eventManager.notify(EventManager.EventType.Message, lotteryResult);
return lotteryResult;
}
protected abstract LotteryResult doDraw(String uId);
}
public class LotteryServiceImpl extends LotteryService {
private MinibusTargetService minibusTargetService = new MinibusTargetService();
@Override
protected LotteryResult doDraw(String uId) {
// 摇号
String lottery = minibusTargetService.lottery(uId);
// 结果
return new LotteryResult(uId, lottery, new Date());
}
}
// 测试
@Test
public void testDesign() {
com.noob.demo.observer.design.event.service.LotteryService lotteryService = new com.noob.demo.observer.design.event.service.LotteryServiceImpl();
LotteryResult result = lotteryService.draw("2765789109876");
logger.info("测试结果:{}", JSON.toJSONString(result));
}
上述实现是基于比较完善的代码设计思路(考虑的业务场景比较完善),此处为了简单理解观察者模式的整体架构,简单结合思路梳理一下一些伪代码实现说明(可从简单案例去理解,再嵌入业务场景分析)
- 事件监听:事件监听定义EventListener、对应两个监听事件的实现MessageEventListener、MQEventListener
- 事件处理:EventManager事件管理类定义
- 具体的业务流程:业务接口定义LotteryService、业务接口实现LotteryServiceImpl
- 测试接口代码
// 事件监听接口定义
public interface EventListener {
// 监听结果,执行指定的操作
void doEvent(LotteryResult result);
}
// 事件监听接口实现(发送短信、发送MQ消息)
public class MessageEventListener implements EventListener {
@Override
public void doEvent(LotteryResult result) {}
}
public class MQEventListener implements EventListener {
@Override
public void doEvent(LotteryResult result) {}
}
通过观察者设计模式改造后,拆分出了核⼼流程与辅助流程的代码。⼀般代码中的核⼼流程不会经常变化。但辅助流程会随着业务的各种变化⽽变化,包括; 营销 、 裂变 、 促活 等等,因此使⽤设计模式架设代码就显得⾮常有必要。
此种设计模式从结构上是满⾜开闭原则的,当你需要新增其他的监听事件或者修改监听逻辑,是不需要改动事件处理类的。但是可能你不能控制调⽤顺序以及需要做⼀些事件结果的返回继续操作,所以使⽤的过程时需要考虑场景的合理性。
任何⼀种设计模式有时候都不是单独使⽤的,需要结合其他模式共同建设。另外设计模式的使⽤是为了让代码更加易于扩展和维护,不能因为添加设计模式⽽把结构处理更加复杂以及难以维护。这样的合理使⽤的经验需要⼤量的实际操作练习⽽来
案例2:红绿灯
场景分析
司机和行人观察红绿灯状态改变,随后做出相应的动作
- 观察者(司机、行人):定义Observer接口,Driver、Pedestrian实现Observer接口
- 被观察者(红绿灯):定义Subject接口(添加观察者、移除观察者、通知观察者)、定义红绿灯实现Subject接口
- Demo测试:分别定义观察者和被观察者,随后根据不同场景测试状态变化通知对应的观察者做出动作
idea生成类图:选择指定类,随后右键选择“Diagrams”—“Show Diagram”
/**
* 观察者:监听接口定义
*/
public interface Observer {
void change(String message);
}
/**
* 观察者:行人
*/
public class Pedestrian implements Observer{
@Override
public void change(String message) {
System.out.println("行人观察到"+message);
}
}
/**
* 观察者:司机
*/
public class Driver implements Observer{
@Override
public void change(String message) {
System.out.println("司机观察到"+message);
}
}
/**
* 被观察者:接口定义(添加、移除、通知观察者对象)
*/
public interface Subject {
/**
* 添加观察者
* @param observer 观察者对象
*/
void addObserver(Observer observer);
/**
* 删除观察者
* @param observer 观察者对象
*/
void deleteObserver(Observer observer);
/**
* 通知所有的观察者
* @param message 通知
*/
void informObservers(String message);
}
/**
* 被观察者对象实现:红绿灯
*/
public class TrafficLight implements Subject {
// 存放观察者集合
private List<Observer> observerList = new ArrayList<>();
// 添加观察者
@Override
public void addObserver(Observer observer) {
observerList.add(observer);
}
// 删除观察者
@Override
public void deleteObserver(Observer observer) {
observerList.remove(observer);
}
// 通知
@Override
public void informObservers(String message) {
for (Observer observer : observerList) {
observer.change(message);
}
}
}
public class Demo {
public static void main(String[] args) {
// 创建被观察者对象(红绿灯对象)
TrafficLight trafficLight = new TrafficLight();
// 创建观察者对象(行人和司机)
Driver driver = new Driver();
Pedestrian pedestrian = new Pedestrian();
// 添加观察者对象
trafficLight.addObserver(driver);
trafficLight.addObserver(pedestrian);
// 红绿灯状态改变需通知其他观察者
trafficLight.informObservers("红绿灯状态改变");
System.out.println("........................");
trafficLight.informObservers("红绿灯状态再次改变了");
System.out.println("........................");
// 司机开走了,移除司机观察者,随后再次通知
trafficLight.deleteObserver(driver);
trafficLight.informObservers("红绿灯状态第三次改变");
}
}
// 测试结果
司机观察到红绿灯状态改变
行人观察到红绿灯状态改变
........................
司机观察到红绿灯状态再次改变了
行人观察到红绿灯状态再次改变了
........................
行人观察到红绿灯状态第三次改变
对比案例1,此处红绿灯案例可以更好地理解观察者模式的思想,核心在于抽离核心业务主链路和非核心业务主链路,以通知的方式告诉观察者要做些什么操作,后续业务逻辑迭代只需要定义观察者对象随后加入观察,即可通知到位。
对比案例1的实现,此处是通过接口定义规范,可结合实际业务需求调整为abstract class形式,进而扩展自身更多的业务逻辑定义