背景
最近公司有一个播报打印的功能,需要上线,功能更是比较简单,就是根据不同的订单类型进行一个报文的组合,最后将报文推送到阿里云平台。细细思索一番,其实能感觉到,不论播报还是打印其实都是一个推送的行为,所有只要将推送的步骤拆分出来,由子类或者父类去实现,并按顺序去执行每个步骤。就可以很方便的维护和扩展,所以就考虑使用模板方法进行设计
模式介绍
模板方法
通常定义就是一个算法的步骤,并允许子类为一个或多个步骤进行实现。通俗讲,就是定义好一个行为的轮廓和骨架,有自由选择是否对其重写
实战
首先思考,播报打印的一个流程大概有几个步骤,基于我们现在的业务,大概可以分为 1.获取设备列表 2.解析报文 3.推送到阿里云
typescript
@Slf4j
public abstract class IotTemplate {
...对象引入
List<GetDeviceDTO> findDeviceList(Integer orderType, Integer stationId, String userId){
if (Objects.isNull(stationId)) {
throw OilCommonException.INVALID_PARAM_ERROR.newInstance("油站id不存在");
}
// 根据收银员id和油站id获取设备sn
List<GetDeviceDTO> allDevice = oilEquipmentSnDAO.getDeviceByStationId(orderType,stationId);
if (!StringUtils.isBlank(userId)) {
List<GetDeviceDTO> device = oilEquipmentSnDAO.getDevice(userId, orderType,stationId);
allDevice.addAll(device);
}
return allDevice;
}
/**
* 设备推送
* @param content 文本
* @param getDeviceDTO 设备信息
*/
public abstract AliyunMqttPushModel aliyunPush(String logo, String content, GetDeviceDTO getDeviceDTO);
/**
* 解析文本
* @return 打印文本
*/
public abstract String explainContent(ExplainContentForm explainContentForm);
}
由于获取设备是个比较通用的逻辑,所以可以选择直接实现,子类只需要继承即可,有了算法行为,我们还需要一个模板方法,将所有行为按一定的顺序进行执行
scss
public void push(IotTemplateForm iotTemplateForm){
LogUtil.info(log,"IotTemplate.push >> param:{}",iotTemplateForm);
Integer stationId = iotTemplateForm.getStationId();
String orderSn = iotTemplateForm.getOrderSn();
String userId = iotTemplateForm.getUserId();
Integer orderType = iotTemplateForm.getOrderType();
String requestId = OilIdUtil.createOnlyId(IdRuleEnum.ALI_MSG.getPrefix());
if (Objects.isNull(stationId)) {
throw OilCommonException.INVALID_PARAM_ERROR.newInstance("油站id不存在");
}
OilOrderDO order = getOrder(orderSn);
// 获取设备列表
List<GetDeviceDTO> allDevice = findDeviceList(orderType,stationId,userId);
// 钩子函数
filterDevice(allDevice);
allDevice.forEach(d -> {
JSONObject orderJson = JSONObject.parseObject(JSONObject.toJSONString(order));
ExplainContentForm explainContentForm = new ExplainContentForm();
explainContentForm.setContent(d.getContent());
explainContentForm.setOrderJson(orderJson);
explainContentForm.setTicketModelList(iotTemplateForm.getTicketModelList());
// 文本解析
String content = explainContent(explainContentForm);
LogUtil.info(log, "IotTemplate.push >> 开始推送 >> content = {} device = {}", content,d);
// 异步推送
push(orderSn, requestId, d, content,iotTemplateForm.getLogo());
});
}
@Async(value = "aliyunPushAsyncExecutor")
public void push(String orderSn, String requestId, GetDeviceDTO d, String content,String logo) {
// 插入推送消息记录
insertMessageInfo(orderSn, BizTypeEnum.VOICEDOWNLOAD.getValue(), d, requestId, content);
// 阿里云推送
AliyunMqttPushModel aliyunMqttPushModel = aliyunPush(logo,content, d);
// 记录数据推送状态
updateMessageInfo(aliyunMqttPushModel, requestId);
}
/**
* 钩子函数 过滤设备
* @param allDevice 设备列表
*/
public abstract void filterDevice(List<GetDeviceDTO> allDevice);
这样子就完成了一个简易的算法模板类,之后只需要继承这个类,重写需要实现的算法行为,就可以很快速的完成整个推送的流程
ini
/**
* 打印模板
*
*/
@Slf4j
@AllArgsConstructor
@Service
public class PrinterTemplate extends IotTemplate {
@Override
public String explainContent(ExplainContentForm explainContentForm) {
List<TicketModel> ticketModelList1 = explainContentForm.getTicketModelList();
List<TicketModel> ticketModelList = BeanUtil.copyToList(ticketModelList1, TicketModel.class);
JSONObject orderJson = explainContentForm.getOrderJson();
StringBuilder receipt = new StringBuilder();
TicketModel title = ticketModelList.remove(0);
receipt.append(ReceiptConstants.logo);
receipt.append(String.format(ReceiptConstants.title, title.getLabelValue()));
receipt.append(ReceiptConstants.underline);
ticketModelList.forEach(ticketModel -> {
if (CollectionUtils.isEmpty(ticketModel.getChild())) {
return;
}
ticketModel.getChild().forEach(childTicketModel -> {
if (childTicketModel.getLabelName().contains("二维码")) {
receipt.append(String.format(ReceiptConstants.qrCode, explainQrCode(childTicketModel.getLabelValue())));
return;
}
receipt.append(String.format(ReceiptConstants.content,
childTicketModel.getLabelName(), TicketLabelEnum.getColName(childTicketModel.getLabelValue(), orderJson)));
});
receipt.append(ReceiptConstants.underline);
});
receipt.append(ReceiptConstants.movePaper);
return receipt.toString();
}
@Override
public void filterDevice(List<GetDeviceDTO> allDevice) {
List<GetDeviceDTO> collect = allDevice.stream().filter(getDeviceDTO -> getDeviceDTO.getHasPrint() == 2).collect(Collectors.toList());
allDevice.removeAll(collect);
}
private String explainQrCode(String labelValue) {
byte[] bytes = HttpUtil.downloadBytes(labelValue);
bytes = bytes == null ? new byte[0] : bytes;
InputStream inputStream = new ByteArrayInputStream(bytes);
String decode = QrCodeUtil.decode(inputStream);
LogUtil.info(log, "PrinterTemplate.explainQrCode >> labelValue:{},decode:{}", labelValue, decode);
return decode;
}
@Override
public AliyunMqttPushModel aliyunPush(String logo, String content, GetDeviceDTO device) {
LogUtil.info(log, "PrinterTemplate.aliyunPush >> content:{} device:{}", content, device);
String requestId = OilIdUtil.createOnlyId(IdRuleEnum.ALI_MSG.getPrefix());
JSONObject resultMap = new JSONObject();
resultMap.put("platformType", 3);
resultMap.put("source", 3);
resultMap.put("orderId", requestId);
resultMap.put("content", content);
resultMap.put("uplogo", logo);
resultMap.put("cn", 1);
AliyunMqttPushForm form = new AliyunMqttPushForm();
if (StringUtils.isNotBlank(device.getIotInstanceId())) {
form.setIotInstanceId(sysConfig.getIotInstanceId());
}
form.setProductKey(device.getProductKey());
form.setDeviceName(device.getDeviceName());
form.setSn(device.getSn());
form.setContent(JSONObject.toJSONString(resultMap));
return aliyunMqttPushManager.pushMessage(form);
}
}
如果后续还有打印的扩展,甚至可以直接继承PrinterTemplate方法,根据自己的需求添加新的逻辑。
例如我有一个新的补打逻辑,整体打印逻辑与原先的打印逻辑一致,但是有个获取订单的逻辑稍微有点区别,我就可以选择直接继承PrinterTemplate方法,去重写其中获取订单的方法即可
less
@Slf4j
@AllArgsConstructor
@Service
public class RePrinterTemplate extends PrinterTemplate {
private OilOrderDAO oilOrderDAO;
private OilUsersDAO oilUsersDAO;
private OilStationDAO oilStationDAO;
@Override
protected OilOrderDO getOrder(String orderSn) {
OilOrderDO oilOrderDO = oilOrderDAO.getOilOrderByOrderNo(orderSn);
//油站名称
OilStationDO oilStationDO = oilStationDAO.getStationById(oilOrderDO.getStationId());
if (Objects.nonNull(oilStationDO) && StringUtils.isNotEmpty(oilStationDO.getStationName())) {
oilOrderDO.setStationName(oilStationDO.getStationName());
}
//收银员名称
if (Objects.isNull(oilOrderDO.getUserId()) || IntegerPool.ZERO.equals(oilOrderDO.getUserId())) {
oilOrderDO.setUserName("管理员");
} else {
OilUsersDO oilUsersDO = oilUsersDAO.getOilUserById(oilOrderDO.getUserId());
if (Objects.nonNull(oilUsersDO)) {
if (Objects.equals(0, oilUsersDO.getBelongId())) {
oilOrderDO.setUserName("管理员");
} else if (StringUtils.isNotEmpty(oilUsersDO.getUsername())){
oilOrderDO.setUserName(oilUsersDO.getUsername());
}
}
}
return oilOrderDO;
}
}
总结
总结来说模板方法主要包含
抽象类:负责给出一个算法的轮廓和骨架,它由一个模板方法和若干个基本方法构成。
模板方法:定义了算法的骨架,按某种顺序调用执行不同基本方法
基本方法:是实现算法各个步骤的方法,可以是抽象方法,也可以是普通方法,再模板方法中进行组合
具体子类:实现抽象类中所定义的抽象方法和钩子方法
其实设计模式更多是一个规范,规范每个人的开发行为,遵循这个规范,可以使得各个开发者的沟通成本快速下降