设计模式思维拓展与实战:观察者模式
今天我们聊的设计模式是观察者模式,在很多开源的框架中都有它的影子,比如大名鼎鼎的Spring,它的源码中涉及到了很多listener和event,同时 JDK 源码也有涉及它的思想,比如 awt 包下面的窗口事件、按钮事件等等,这都体现了观察者模式的重要性,所以我们要掌握它的设计原理,并在实际应用中精通它,捅破这层窗户纸,好了,废话不多说,咱们还是看业务场景,我们在网上购买商品时,最后一步下订单操作完成之后,系统会为我们生成订单、扣款、发短信等等,其实无论有多少步操作,都是属于同一个事件,就是下单事件,而这些一个个的步骤,我们可以把它们看成该事件的观察者(生成订单观察者、扣款观察者和发短信观察者),同时我们也可以把用户下单的操作看成是事件源,事件源控制着观察者的发布与订阅,当然也可以说成是事件源控制着观察者的启动与注册,老路子,我们还是直接看流程图和代码说明。
咱们看一下上面的流程图,三个观察者(生成订单观察者、支付观察者和发送短信观察者)被注册到了下单事件源中,然后事件源发布了下单的事件,并把该事件以同步或者异步的方式通知到每一个观察者,大家只看流程图可能不容易理解,先别着急,我们接下来看代码说明:
首先我们先创建一个下单数据实体类OrderData,里面有两个属性,消费金额和购买商品数量: 注: @Data @AllArgsConstructor 这两个注解是 lombok 包里面的,引入这个包就不用再写 set、get 方法和构造函数了。
package com.observer.resource; import java.math.BigDecimal; import lombok.AllArgsConstructor; import lombok.Data; /** * 订单数据 */ @Data @AllArgsConstructor public class OrderData { /** * 消费金额 */private BigDecimal money; /** * 购买商品个数 */private int buyCount; }
接下来创建一个PlaceOrderEvent类作为下单事件,里面有一个成员变量,也就是刚刚创建的订单数据实体类:
package com.observer.event; import com.observer.resource.OrderData; import lombok.AllArgsConstructor; import lombok.Data; /** * 下单事件 */ @Data @AllArgsConstructor public class PlaceOrderEvent{ private OrderData orderData; }
然后我们继续创建下单的观察者接口PlaceOrderObserver,里面有一个方法actionPlaceOrder,用于执行下单的具体操作,并把PlaceOrderEvent作为参数传进来:
package com.observer.service; import com.observer.event.PlaceOrderEvent; /** * 下单观察者 */ public interface PlaceOrderObserver{ /** * 触发下单操作 * @param e */void actionPlaceOrder(PlaceOrderEvent e); }
接着我们要创建三个类用于模拟生成订单、支付和发送短信业务,并且同时实现PlaceOrderObserver接口,代表它们都是这个事件的观察者: 注: @Slf4j 注解也是 lombok 里面的
GenerateOrdersObserver 生产订单的观察者类:
package com.observer.service.impl; import com.observer.event.PlaceOrderEvent; import com.observer.resource.OrderData; import com.observer.service.PlaceOrderObserver; import lombok.extern.slf4j.Slf4j; /** * 生成订单观察者类 */ @Slf4j public class GenerateOrdersObserver implements PlaceOrderObserver { @Overridepublic void actionPlaceOrder(PlaceOrderEvent e) { this.addOrder(e.getOrderData()); } /** * 生成订单 * @param orderData */private void addOrder(OrderData orderData){ log.info("生成订单成功,订单金额:{},购买商品数:{}", orderData.getMoney(),orderData.getBuyCount()); } }
PayObserver 支付观察者类
package com.observer.service.impl; import com.observer.event.PlaceOrderEvent; import com.observer.resource.OrderData; import com.observer.service.PlaceOrderObserver; import lombok.extern.slf4j.Slf4j; /** * 支付观察者类 */ @Slf4j public class PayObserver implements PlaceOrderObserver { @Overridepublic void actionPlaceOrder(PlaceOrderEvent e) { this.payMoney(e.getOrderData()); } /** * 支付 * @param orderData */private void payMoney(OrderData orderData){ log.info("支付成功{}元",orderData.getMoney()); } }
SendMsgObserver 发送短信观察者类
package com.observer.service.impl; import com.observer.event.PlaceOrderEvent; import com.observer.resource.OrderData; import com.observer.service.PlaceOrderObserver; import lombok.extern.slf4j.Slf4j; /** * 发送短信观察者类 */ @Slf4j public class SendMsgObserver implements PlaceOrderObserver { @Overridepublic void actionPlaceOrder(PlaceOrderEvent e) { this.sendMsg(e.getOrderData()); } /** * 发送短信 * @param orderData */private void sendMsg(OrderData orderData){ log.info("发送短信成功,短信内容:购买商品数为{},总共消费了{}元", orderData.getBuyCount(),orderData.getMoney()); } }
三个观察者类创建后,接着我们要创建一个事件源接口类TriggerSource用于发布和订阅观察者,该接口有两个方法,第一个方法startUpObserver用来启动观察者或者说是发布这个事件,第二个方法registObserver是观察者注册,只有先注册了观察者才可以被发布或启动:
package com.observer.triggerObserver; import com.observer.event.PlaceOrderEvent; import com.observer.service.PlaceOrderObserver; /** * 触发观察者启动顶层类 */ public interface TriggerSource { /** * 启动观察者(发布功能) * @param event */void startUpObserver(PlaceOrderEvent event); /** * 注册观察者(订阅功能) * @param observer */void registObserver(PlaceOrderObserver observer); }
然后我们继续创建一个事件源接口的实现类PlaceOrderTriggerSource,用于专门发布事件和订阅观察者:
package com.observer.triggerObserver; import com.google.common.collect.Lists; import com.observer.event.PlaceOrderEvent; import com.observer.service.PlaceOrderObserver; import com.observer.event.Event; import java.util.List; /** * 观察下单启动类 */ public class PlaceOrderTriggerSource implements TriggerSource{ private List<PlaceOrderObserver> observerList = Lists.newArrayList();//被观察者列表 @Overridepublic void startUpObserver(PlaceOrderEvent event) { for(PlaceOrderObserver observer : observerList){ observer.actionPlaceOrder(event);//循环执行各个观察者方法 } } @Overridepublic void registObserver(PlaceOrderObserver observer) { observerList.add(observer);//添加到观察者容器中 } }
简单说明一下PlaceOrderTriggerSource类,因为要全部执行和该事件相关的观察者方法,所以startUpObserver函数采用 for 循环的方式迭代执行,这也是观察者模式比较重要的一点,observerList是存放观察者的容器,第二个方法registObserver负责把触发该事件的观察者添加到容器中。
最后我们写一个测试类,来进行测试:
package com.test; import com.observer.event.PlaceOrderEvent; import com.observer.resource.OrderData; import com.observer.service.impl.GenerateOrdersObserver; import com.observer.service.impl.PayObserver; import com.observer.service.impl.SendMsgObserver; import com.observer.triggerObserver.PlaceOrderTriggerSource; import com.observer.triggerObserver.TriggerSource; import org.junit.Test; import java.math.BigDecimal; public class TestObserver { @Testpublic void testObserver(){ TriggerSource placeOrderTriggerObserver = new PlaceOrderTriggerSource(); //把观察者对象添加到启动观察者实例中,相当于订阅 placeOrderTriggerObserver.registObserver(new GenerateOrdersObserver()); placeOrderTriggerObserver.registObserver(new PayObserver()); placeOrderTriggerObserver.registObserver(new SendMsgObserver()); //实例化下单事件类,并且设置订单数据 PlaceOrderEvent event = new PlaceOrderEvent(new OrderData(BigDecimal.valueOf(1000),20)); //把下单事件传给所有的观察者,然后执行他们自己的业务逻辑,相当于发布 placeOrderTriggerObserver.startUpObserver(event); } }
打完收工,我们来看一下执行结果:
![](https://assets.matters.news/embed/d0d0e85b-f839-4e1b-860b-79c9141e5231.png)
ok,执行没有问题,假如下单操作中又增加了一项积分的业务,我们只需要实现一个积分的观察者同时把它注册到下单的事件源中就可以了,同样的,如果不需要短信业务类,那么只需要取消掉短信观察者的注册就可以了,无论是增加还是取消业务,都能减少各模块的的耦合性,观察者模式我们今天就聊到这里,欢迎大家留言哦,如果不错的请给一个好评,之后还有更多精彩的内容分享给大家,我在 gitchat 还有一篇文章是专门讲解策略模式实战的,在 spring 框架下如何使用策略模式,同时利用注解和枚举的方式把网关思想融入了进去,从而实现代码零侵入的业务扩展功能