今天我们聊的设计模式是观察者模式,在很多开源的框架中都有它的影子,比如大名鼎鼎的Spring,它的源码中涉及到了很多listener和event,同时JDK源码也有涉及它的思想,比如awt包下面的窗口事件、按钮事件等等,这都体现了观察者模式的重要性,所以我们要掌握它的设计原理,好了,废话不多说,咱们看业务场景:比如我们在网上购买商品时,最后一步下订单操作完成之后,系统会为我们生成订单、扣款、发短信等等,其实无论有多少步操作,都是属于同一个事件,就是下单事件,而这些一个个的步骤,我们可以把它们看成该事件的观察者(生成订单观察者、扣款观察者和发短信观察者),同时我们也可以把用户下单的操作看成是事件源,也就是事件的发生来源,事件源控制着观察者的发布与订阅,当然也可以说成是事件源控制着观察者的启动与注册,我们直接看代码说明。
首先我们先创建一个下单数据实体类OrderData,里面有两个属性,消费金额和购买商品数量:
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接口,代表它们都是这个事件的观察者:
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 {
@Override
public void actionPlaceOrder(PlaceOrderEvent e) {
this.addOrder(e.getOrderData());
}
/**
* 生成订单
* @param orderData
*/
private void addOrder(OrderData orderData){
log.info("生成订单成功,订单金额:{},购买商品数:{}",
orderData.getMoney(),orderData.getBuyCount());
}
}
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 {
@Override
public void actionPlaceOrder(PlaceOrderEvent e) {
this.payMoney(e.getOrderData());
}
/**
* 支付
* @param orderData
*/
private void payMoney(OrderData orderData){
log.info("支付成功{}元",orderData.getMoney());
}
}
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 {
@Override
public 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 observerList = Lists.newArrayList();
@Override
public void startUpObserver(PlaceOrderEvent event) {
for(PlaceOrderObserver observer : observerList){
observer.actionPlaceOrder(event);//循环执行各个观察者的方法
}
}
@Override
public 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 {
@Test
public 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);
}
}
ok,执行没有问题,假如下单操作中又增加了一项积分的业务,我们只需要实现一个积分的观察者同时把它注册到下单的事件源中就可以了,同样的,如果不需要短信业务类,那么只需要取消掉短信观察者的注册就可以了,无论是增加还是取消业务,都能减少各模块的的耦合性,观察者模式我们今天就聊到这里,如果有写的不好的地方,还请各位高手留言指正,然后我们一起讨论,这是我的私人公主号:jiagoushixiulian777,欢迎大家关注,谢谢大家!