Java策略模式实践

1 什么是策略模式

策略模式(Strategy Pattern):一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。

在策略模式定义了一系列算法或策略,并将每个算法封装在独立的类中,使得它们可以互相替换。通过使用策略模式,可以在运行时根据需要选择不同的算法,而不需要修改客户端代码。

策略模式可以优雅的解决if-else所带来的复杂和难以维护的问题,并且拥有很好的可扩展性。

2 策略模式的构成

策略模式包含以下几个核心角色:

  • 环境上下文(Context):维护一个对策略对象的引用,负责将客户端请求委派给具体的策略对象执行。环境类可以通过依赖注入、简单工厂等方式来获取具体策略对象。
  • 抽象策略(Abstract Strategy):定义了策略对象的公共接口或抽象类,规定了具体策略类必须实现的方法。
  • 具体策略(Concrete Strategy):实现了抽象策略定义的接口或抽象类,包含了具体的算法实现。

策略模式通过将算法与使用算法的代码解耦,提供了一种动态选择不同算法的方法。客户端代码不需要知道具体的算法细节,而是通过调用环境类来使用所选择的策略。

举例:

定义活动的 Strategy 接口和实现了 Strategy 接口的实体策略类。Context 是一个使用了某种策略的类。

StrategyPatternDemo,我们的演示类使用 Context 和策略对象来演示 Context 在它所配置或使用的策略改变时的行为变化。

image-20240410181831812

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的工具容器:

image-20240410185508323

枚举类RouteStrategyEnum记录了通知渠道的BeanName以及全限定类名:

image-20240410190224519

因为我们提供了两种获取策略对象的方式:1是通过SpringContext获取ioc容器中注册的策略;2是通过枚举类中的全限定名反射得到策略对象。如下所示:

image-20240410185138732

image-20240410185945143

再来看策略类公共接口:

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;
    }
}

当前仅接入了短信通知和邮件通知服务,策略类如下:

image-20240411101354468

image-20240411101534764

到此为止所有前置类都准备就绪,重点看下RouteContext#route方法,逻辑是执行上下文环境中的策略对象的route方法,开新线程对通知记录落库。

image-20240411102255658

入参instance记录了一个通知实例的详细信息:

image-20240411102224522

最后看下RouteContext是怎么应用的:首先new RouteContext()对象,加载通知请求中的路由信息,此处通知方法都是最小粒度单位,传参是单个通知实例。至于批量通知和多途径通知,由任务调度服务实现。

image-20240411103322069

这里RouteContext的构建和加载策略类的方式还不够优雅,后续可以改为建造者模式。


本博客内容仅供个人学习使用,禁止用于商业用途。转载需注明出处并链接至原文。

posted @ 2024-04-12 16:31  爱吃麦辣鸡翅  阅读(21)  评论(0编辑  收藏  举报