Java策略模式实践
1 什么是策略模式
策略模式(Strategy Pattern):一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
在策略模式定义了一系列算法或策略,并将每个算法封装在独立的类中,使得它们可以互相替换。通过使用策略模式,可以在运行时根据需要选择不同的算法,而不需要修改客户端代码。
策略模式可以优雅的解决if-else所带来的复杂和难以维护的问题,并且拥有很好的可扩展性。
2 策略模式的构成
策略模式包含以下几个核心角色:
- 环境上下文(Context):维护一个对策略对象的引用,负责将客户端请求委派给具体的策略对象执行。环境类可以通过依赖注入、简单工厂等方式来获取具体策略对象。
- 抽象策略(Abstract Strategy):定义了策略对象的公共接口或抽象类,规定了具体策略类必须实现的方法。
- 具体策略(Concrete Strategy):实现了抽象策略定义的接口或抽象类,包含了具体的算法实现。
策略模式通过将算法与使用算法的代码解耦,提供了一种动态选择不同算法的方法。客户端代码不需要知道具体的算法细节,而是通过调用环境类来使用所选择的策略。
举例:
定义活动的 Strategy 接口和实现了 Strategy 接口的实体策略类。Context 是一个使用了某种策略的类。
StrategyPatternDemo,我们的演示类使用 Context 和策略对象来演示 Context 在它所配置或使用的策略改变时的行为变化。
3 策略模式实践
在我司的通知服务中,涉及到不同渠道的通知服务,例如:钉钉,短信,邮箱,微信等,所以定义抽象通知渠道,具体的通知渠道类继承并丰富其细节:
public abstract class AxxNotifyRoute implements NotifyRoute {
/**
* 通知渠道,指具体的提供通知服务的公司,例如使用了阿里云的短信网关,则notifyChannel则为aliyun
*/
String notifyChannel;
/**
* 通知途径,指具体的通知载体
* 短信:SMS
* 电话:PHONE
* 钉钉:DINGDING
* 邮箱:EMAIL
* 微信:WECHAT
* 站内信:INNER_MESSAGE
* APP:APP
*/
String routeType;
/**
* 通知地址
*/
String routeURI;
}
首先看Context上下文环境,我们需要一个RouteContext
来决定路由到哪种通知渠道、加载路由策略以及执行路由方法:
public class RouteContext {
private RouteStrategy routeStrategy;
public RouteContext(RouteStrategy routeStrategy) {
this.routeStrategy = routeStrategy;
}
public RouteContext() {
}
/**
* 加载路由策略,通过spring容器获取
*
* @param route
* @return
*/
public void loadRouteStrategy(NotifyRoute route) {
doLoad(route);
}
private void doLoad(NotifyRoute route) {
if (route == null) return;
for (RouteStrategyEnum value : RouteStrategyEnum.values()) {
if (route.getRouteType().equals(value.getTypeName())) {
routeStrategy = (RouteStrategy) SpringContext.getBean(value.getObjName());
return;
}
}
}
/**
* 加载 RouteStrategy 通过反射的方式
*
* @param route
*/
@SneakyThrows
private void doLoadFromClass(NotifyRoute route) {
if (route == null) return;
for (RouteStrategyEnum value : RouteStrategyEnum.values()) {
if (route.getRouteType().equals(value.getTypeName())) {
Class<?> clazz = Class.forName(value.getClazzName());
routeStrategy = (RouteStrategy) clazz.getDeclaredConstructor().newInstance();
return;
}
}
}
/**
* 渠道路由和通知记录落库
* @param instance
* @return
*/
public NotificationRouteResult route(NotificationInstance instance) {
LocalDateTime now = LocalDateTimeUtil.now();
NotificationRouteResult routeResult = routeStrategy.route(instance);
new Thread(() -> NotificationRecordHandler.recordNotification(instance, routeResult, now)).start();
return routeResult;
}
}
其中SpringContext
是一个获取上下文以及ioc容器内Bean的工具容器:
枚举类RouteStrategyEnum
记录了通知渠道的BeanName以及全限定类名:
因为我们提供了两种获取策略对象的方式:1是通过SpringContext获取ioc容器中注册的策略;2是通过枚举类中的全限定名反射得到策略对象。如下所示:
再来看策略类公共接口:
public interface RouteStrategy {
NotificationRouteResult route(NotificationInstance instance);
}
返回结果是通知结果状态以及原因:
@Data
public class NotificationRouteResult {
private String notifyStatus;
private String cause;
public NotificationRouteResult(String notifyStatus) {
this.notifyStatus = notifyStatus;
}
public NotificationRouteResult() {
}
public NotificationRouteResult(String notifyStatus, String cause) {
this.notifyStatus = notifyStatus;
this.cause = cause;
}
}
当前仅接入了短信通知和邮件通知服务,策略类如下:
到此为止所有前置类都准备就绪,重点看下RouteContext#route
方法,逻辑是执行上下文环境中的策略对象的route
方法,开新线程对通知记录落库。
入参instance记录了一个通知实例的详细信息:
最后看下RouteContext
是怎么应用的:首先new RouteContext()对象,加载通知请求中的路由信息,此处通知方法都是最小粒度单位,传参是单个通知实例。至于批量通知和多途径通知,由任务调度服务实现。
这里RouteContext
的构建和加载策略类的方式还不够优雅,后续可以改为建造者模式。
本博客内容仅供个人学习使用,禁止用于商业用途。转载需注明出处并链接至原文。