23种设计模式---策略模式,点餐项目实战
策略模式定义了一系列的算法,并将每一个算法封装起来,使它们可以相互替换。
策略模式通常包含以下角色:
1.抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
2.具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
3.环境(Context)类:持有一个策略类的引用,最终给客户端调用。
背景:
xx点餐项目-致力于校园点餐,需要开发"消息通知"功能
1> 消息触发时机:支付成功、退款成功、已送到、已完成
2> 多宿主:完校APP、完校微信小程序
需要在上述触发时机,根据不同宿主发送不同的消息通知。
完校APP---发送极光Jpush + 应用内通知;
完校微信小程序---发送微信模板消息
因为后续还可能新增其他宿主环境,所以决定在后端项目使用策略模式来维护一系列宿主发送消息的策略。
反思问题:
针对发送消息通知,开发地项目代码
1> SendNotification接口
2> WanXiaoSendNotification、WechatAppletSendNotification实现接口
3> NotificationUtil业务类---异步线程池发送消息通知、避免重复发送判断。
反思可知:
之前书写的'点餐消息通知-策略模式'是阉割版的,缺失了StrategyContext(类似策略族使用门户),导致封装不够!
坏处:站在client角度,暴露了过多策略模式实现细节,client使用具体策略较为复杂。
改进:
/**
* 点餐通知-策略模式上下文
* @author jinhaodong
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class SendNotificationContext {
// 抽象接口声明
private SendNotification notification;
// 发送通知-具体策略组
private final WanXiaoSendNotification wanXiaoSendNotification;
private final WechatAppletSendNotification wechatAppletSendNotification;
/**
* 依据'订单宿主'确定具体策略,同时发送消息通知
* @param sealmainHost 订单宿主
*/
public void sendNotification(String sealmainHost){
switch (sealmainHost){
case "wanxiao":
notification = wanXiaoSendNotification;
break;
case "miniprogram":
notification = wechatAppletSendNotification;
break;
default:
break;
}
notification.sendNotification(scene, sealmain);
}
}//end class
客户端client业务代码,使用策略上下文
/**
* 客户端发送消息通知-业务使用
* @author jinhaodong
*/
@Slf4j
@Component("notificationUtil")
@RequiredArgsConstructor
public class NotificationUtil {
// 策略上下文------核心位置
private final SendNotificationContext sendNotificationContext;
private final SealService sealService;
private final OrderNotificationService orderNotificationService;
/**
* 依据订单宿主,发送点餐通知
* @param scene 点餐通知场景
* @param billno 订单号
* @param tenantDetails 指定数据库模式
*/
@Async("asyncSendNotificationExecutor")
public void sendNotification(NotificationSceneEnum scene, String billno, TenantDetails tenantDetails){
// 1.设置异步线程,租户信息
TenantContextHolder.getContext().setTenant(tenantDetails);
log.info("当前线程:[{}],对应的租户信息:[{}]", Thread.currentThread().getName(), JSONObject.toJSONString(TenantContextHolder.getContext().getTenant()));
// 2.避免重复发送
if(isAlreadySendNotification(billno, scene)){
log.error("订单号:[{}]在场景:[{}]已发送过通知了,不能重复发送!", billno, scene.getScene());
}else{
// 3.根据订单宿主,指定具体发送策略
TSealmain sealmain = sealService.getSealmainFromAll(billno);
// 使用策略上下文,发送通知------核心位置
sendNotificationContext.sendNotification(sealmain.getHost());
}
}
/**
* 私有方法:订单-指定场景下,是否已发送通知
* @param billno 订单号
* @param scene 场景枚举
* @return
*/
private boolean isAlreadySendNotification(String billno, NotificationSceneEnum scene){
boolean result = false;
List<TOrderNotification> orderNotifications = orderNotificationService.getByBillNoAndScene(billno, scene.getScene());
if (orderNotifications.size() > 0){
result = true;
}
return result;
}
}//end class
总结:
1> 策略模式中Strategy接口、ConcreteStrategy具体类,使得策略可相互替换,易拓展、易维护;
2> StrategyContext,封装了具体策略实现细节,为客户端client提供一种简单方式在业务中使用这些策略。
3> NotificationUtil 客户端业务代码,引用策略上下文发送消息通知【有了策略上下文,客户端使用时屏蔽了多余细节,使用起来很简便】。同时这个类更倾向于业务【异步线程池处理、线程隔离属性设置、重复性判断】等。