如何在Spring环境中优雅地使用策略模式

    本文主要介绍策略模式的一般实现以及在Spring环境下如何优雅地使用策略模式。

基本定义

    首先介绍一下策略模式基本定义:首先针对某一问题的解决方法提供一系列的算法或者策略,然后可以根据环境与条件选择不同的算法或者策略来解决问题,同时可以方便的切换调整算法或策略的实现。

    以出行旅游为例,我们可以选择乘坐飞机、高铁、长途客车等方式前往目的地,不同的交通工具(飞机、高铁、长途客车)相当于解决问题(前往旅游目的地)的一系列算法。其UML结构如下:

如何在Spring环境中优雅地使用策略模式
策略模式的UML结构

一般实现

    在此,我们以出行方式为例简要介绍一下策略模式的一般实现。

    /**
     * 出行方式接口
     */

    public interface TripMode {
        void trip();
    }

    /**
     * 出行方式实现:飞机
     */

    public class ByPlane implements TripMode {
        @Override
        public void trip() {
            System.out.println("乘飞机出行");
        }
    }

    /**
     * 出行方式实现:长途客车
     */

    public class ByCoach implements TripMode {
        @Override
        public void trip() {
            System.out.println("乘长途客车出行");
        }
    }

    /**
     * 旅游出行
     */

    public class Travel {
        private final TripMode tripMode;

        public <T extends TripMode> Travel(T tripMode) {
            this.tripMode = tripMode;
        }

        public void trip() {
            this.tripMode.trip();
        }
    }

    /**
     * 测试出行方式
     */

    public class Main {
        public static void main(String[] args) throws Exception {
            Travel travel;
    
            // 某种条件判断成立后,选择飞机出行
            travel = new Travel(new ByPlane());
            travel.trip();
    
            // 某种条件判断成立后,选择飞机出行
            travel = new Travel(new ByCoach());
            travel.trip();
        }
    }

    运行Main.main方法后将得到如下结果:

如何在Spring环境中优雅地使用策略模式
出行方式测试结果

    可以看到,这里需要我们根据上下文条件确定最终的策略实现,实际使用依然存在小部分判断,如果将出行方式先存储在Map<String, TripMode>中,那么每次新增或者调整都需要修改部分代码。整体上来说效果差强人意。下面让我们看看当策略模式与Spring结合在一起会有什么神奇的效果吧!

Spring与策略模式

    在这里,我们以实际业务场景为例来介绍Spring中的策略模式实现方式。

    三维家美家平台的SaaS业务线支持用户授权微信小程序或者公众号到我们的第三方平台美家旺铺,授权成功后,我们将为其「部署一个独立的小程序」。该小程序是「专为家居行业定制的营销获客解决方案,为线下门店提供数字展厅、营销工具、直播工具、方案内容、智能名片等数字化工具以帮助门店获客引流」

    这里的授权指通过微信第三方平台进行公众号/小程序权限授权,当发生公众号/小程序授权、公众号/小程序更新授权、公众号/小程序取消授权等事件时,会将事件通过授权事件接收URL推送过来。

如何在Spring环境中优雅地使用策略模式
微信提供授权事件接收URL

    现在让我们看看在Spring环境下的授权事件处理器实现。

定义处理器接口

public interface AuthEventHandler {
    /**
     * 处理事件
     */

    void handle(AuthEvent authEvent);

    /**
     * 事件处理器监听/支持的事件名称
     */

    String onEvent();

    /**
     * 处理器是否支持处理特定事件
     */

    default boolean support(String event) {
        return StringUtils.equalsIgnoreCase(this.onEvent(), event);
    }
}

    这里的授权事件处理器定义了onEvent方法用于返回该处理器所能处理的事件,handle方法表示事件具体处理实现。

处理器实现与管理器

/**
 * 处理授权事件
 */

@Order(1)
public class AuthorizedEventHandler implements AuthEventHandler {
    private static final String EVENT = "authorized";

    @Override
    public void handle(AuthEvent authEvent) {
        System.out.println("处理授权事件,公众号/小程序的appId为:" + authEvent.getAppId());
    }

    @Override
    public String onEvent() {
        return EVENT;
    }
}

/**
 * 处理终止授权事件
 */

public class UnauthorizedEventHandler implements AuthEventHandler {
    private static final String EVENT = "unauthorized";

    @Override
    public void handle(AuthEvent authEvent) {
        System.out.println("处理终止授权事件,公众号/小程序的appId为:" + authEvent.getAppId());
    }

    @Override
    public String onEvent() {
        return EVENT;
    }
}

    这里我们简单定义了两个处理器实现,其中AuthorizedEventHandler表示处理公众号/小程序的授权逻辑(这里的授权包括首次授权和终止授权后重新授权),UnauthorizedEventHandler表示处理公众号/小程序的终止授权逻辑。这里我们统一简化为文字输出。

    然后有一个「事件处理管理器」,它通过Map<String, AuthEventHandler>持有事件与处理器的映射关系,然后提供统一的事件处理方法。

@Slf4j
@AllArgsConstructor
public class AuthEventManager {
    private final Map<String, AuthEventHandler> authEventHandlerMap;

    public void handle(AuthEvent authEvent) {
        String event = authEvent.getInfoType();
        AuthEventHandler handler = authEventHandlerMap.get(event);
        if (Objects.isNull(handler)) {
            log.error("Not Handler found for event: {}", event);
            return;
        }
        handler.handle(authEvent);
    }
}

关联处理器和管理器

    接着,我们通过一个「核心配置类搜集所有的处理器,然后将他们关联到管理器中」

@Slf4j
@Configuration
public class AuthEventManagerConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public AuthEventManager authEventManager(ObjectProvider<List<AuthEventHandler>> handlersProvider) {
        List<AuthEventHandler> handlers = handlersProvider.getIfAvailable();
        if (CollectionUtils.isEmpty(handlers)) {
            log.warn("None AuthEventHandler found!");
            return new AuthEventManager(Collections.emptyMap());
        }

        // 对所有处理器进行排序(Handler 可以通过实现 org.springframework.core.Ordered 接口或标注
        // @org.springframework.core.annotation.Order或者@Javax.annotation.Priority注解控制顺序,值越小排序越优先)
        AnnotationAwareOrderComparator.sort(handlers);

        // 调整结构为 Map
        Map<String, AuthEventHandler> map = Maps.newHashMapWithExpectedSize(handlers.size());
        for (AuthEventHandler handler : handlers) {
            String event = handler.onEvent();
            AuthEventHandler h = map.get(event);
            if (Objects.nonNull(h)) {
                log.warn("Duplicate handler[{}] found for event: {}. reserve high priority handler: {}",
                        handler.getClass().getSimpleName(), event, h.getClass().getSimpleName());
                continue;
            }
            map.put(event, handler);
        }
        return new AuthEventManager(Collections.unmodifiableMap(map));
    }
}

    解读一下,配置类通过ObjectProvider<List<AuthEventHandler>>搜集所有托管在Spring容器中的AuthEventHandler实例(即搜集实现AuthEventHandler接口并且注册为SpringBean的实例),然后通过AnnotationAwareOrderComparator#sort方法对所有处理器进行一次排序,排序后再根据处理器所支持的事件将其装配成一个Map<String, AuthEventHandler>。最终实例化AuthEventManager并托管到Spring容器中。

验证授权事件管理器

    最后,我们通过以下测试类测试效果:

@RunWith(pringRunner.class)
@SpringBootTest(classes = StrategyApplication.class)
public class StrategyTest {
    @Autowired
    private AuthEventManager authEventManager;

    @Test
    public void testAuthEvent() throws Exception {
        // 模拟微信第三方平台推送过来的授权相关事件
        AuthEvent authorizedEvent = new AuthEvent("wx408a91234f4110b3""authorized");
        authEventManager.handle(authorizedEvent);

        AuthEvent unauthorizedEvent = new AuthEvent("wx408a91234f4110b3""unauthorized");
        authEventManager.handle(unauthorizedEvent);
    }
}

    测试结果如下图所示:

如何在Spring环境中优雅地使用策略模式
Spring策略模式应用测试结果

    通过这个核心配置类,每次添加或者删除处理器,只需要添加或者删除AuthEventHandler实现类即可,不需要调整任何代码即可实现策略的变更。是不是一般实现要灵活很多呢!

延伸扩展

    目前我们通过AuthEventManagerConfiguration实现了策略模式的核心组装逻辑。那么是否可以稍加调整将策略调整成「处理器链模式」呢(即一个业务需要分步骤执行,由一系列处理器提供分步操作支持,每一步操作依赖前一步操作的执行结果),聪明的你是否知道该怎么调整呢!


原文始发于微信公众号(三维家技术实践):如何在Spring环境中优雅地使用策略模式

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/30596.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!