策略模式

定义

定义一簇算法类,将每个算法分别封装起来,让他们可以互相替换,策略模式可以使算法的变化独立于使用它们的客户端

场景

使用策略模式,可以避免冗长的if-else 或 switch分支判断

实现

  1. 策略的定义

    策略的定义需要定义一个策略接口和一组实现这个接口的策略类,因为所有的策略类都实现相同的接口

  1. public interface Strategy{
  2. void algorithm();
  3. }
  4. public class ConcreteStrategyA implements Strategy {
  5. @Override
  6. public void algorithm() {
  7. //具体的算法...
  8. }
  9. }
  10. public class ConcreteStrategyB implements Strategy {
  11. @Override
  12. public void algorithm() {
  13. //具体的算法...
  14. }
  15. }
  1. 策略的创建

    在使用的时候,一般会通过类型来判断创建哪个策略来使用,在策略上下文中,可以使用map维护好策略类

  2. 策略的使用

    策略模式包含一组可选策略,在使用策略时,一般如何确定使用哪个策略呢?最常见的是运行时动态确定使用哪种策略。程序在运行期间,根据配置、计算结果、网络等这些不确定因素,动态决定使用哪种策略

  1. public class StrategyContext{
  2. private static final Map<String, Strategy> strategies = new HashMap<>();
  3. static {
  4. strategies.put("A", new ConcreteStrategyA());
  5. strategies.put("B", new ConcreteStrategyB());
  6. }
  7. private static Strategy getStrategy(String type) {
  8. if (type == null || type.isEmpty()) {
  9. throw new IllegalArgumentException("type should not be empty.");
  10. }
  11. return strategies.get(type);
  12. }
  13. public void algorithm(String type){
  14. Strategy strategy = this.getStrategy(type);
  15. strategy.algorithm();
  16. }
  17. }

UML

策略模式的创建和使用--Spring和自定义注解

在介绍策略模式时,在上下文中使用了map存储好的策略实例,在根据type获取具体的策略,调用策略算法。

当需要添加一种策略时,需要修改context代码,这违反了开闭原则:对修改关闭,对扩展开放。

要实现对扩展开放,就要对type和具体的策略实现类在代码中进行关联,可以使用自定义注解的方式,在注解中指定策略的type。

策略上下文实现类实现 BeanPostProcessor 接口,在该接口中编写策略类型与bean的关系并维护到策略上下文中。

  1. package com.masterlink.strategy;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.springframework.aop.support.AopUtils;
  4. import org.springframework.beans.BeansException;
  5. import org.springframework.beans.factory.config.BeanPostProcessor;
  6. import org.springframework.core.Ordered;
  7. import org.springframework.core.annotation.AnnotatedElementUtils;
  8. import org.springframework.stereotype.Component;
  9. import java.util.Collections;
  10. import java.util.Set;
  11. import java.util.concurrent.ConcurrentHashMap;
  12. @Slf4j
  13. @Component
  14. public class StrategyDemoBeanPostProcessor implements BeanPostProcessor, Ordered {
  15. private final Set<Class<?>> nonAnnotatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>(64));
  16. private final StrategyContext strategyContext;
  17. private StrategyDemoBeanPostProcessor(StrategyContext context) {
  18. this.strategyContext = context;
  19. }
  20. @Override
  21. public int getOrder() {
  22. return LOWEST_PRECEDENCE;
  23. }
  24. @Override
  25. public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
  26. if (!this.nonAnnotatedClasses.contains(bean.getClass())) {
  27. // 获取使用 @StrategyDemo 注解的Class信息
  28. Class<?> targetClass = AopUtils.getTargetClass(bean);
  29. Class<Strategy> orderStrategyClass = (Class<Strategy>) targetClass;
  30. StrategyDemo ann = findAnnotation(targetClass);
  31. if (ann != null) {
  32. processListener(ann, orderStrategyClass);
  33. }
  34. }
  35. return bean;
  36. }
  37. @Override
  38. public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  39. return bean;
  40. }
  41. protected void processListener(StrategyDemo annotation,
  42. Class<Strategy> classes) {
  43. // 注册策略
  44. this.strategyContext
  45. .registerStrategy(annotation.type(), classes);
  46. }
  47. private StrategyDemo findAnnotation(Class<?> clazz) {
  48. StrategyDemo ann = AnnotatedElementUtils.findMergedAnnotation(clazz, StrategyDemo.class);
  49. return ann;
  50. }
  51. }

  1. @Component
  2. public class StrategyContext implements ApplicationContextAware {
  3. private final Map<String, Class<Strategy>> strategyClassMap = new ConcurrentHashMap<>(64);
  4. private final Map<String, Strategy> beanMap = new ConcurrentHashMap<>(64);
  5. private ApplicationContext applicationContext;
  6. /**
  7. * 注册策略
  8. * @param type
  9. * @param strategyClass
  10. */
  11. public void registerStrategy(String type, Class<Strategy> strategyClass){
  12. if (strategyClassMap.containsKey(type)){
  13. throw new RuntimeException("strategy type:"+type+" exist");
  14. }
  15. strategyClassMap.put(type, strategyClass);
  16. }
  17. /**
  18. * 执行策略
  19. * @param type
  20. */
  21. public void algorithm(String type){
  22. Strategy strategy = this.getStrategy(type);
  23. strategy.algorithm();
  24. }
  25. private Strategy getStrategy(String type) {
  26. if (type == null || type.isEmpty()) {
  27. throw new IllegalArgumentException("type should not be empty.");
  28. }
  29. Class<Strategy> strategyClass = strategyClassMap.get(type);
  30. return createOrGetStrategy(type, strategyClass);
  31. }
  32. private Strategy createOrGetStrategy(String type,Class<Strategy> strategyClass ){
  33. if (beanMap.containsKey(type)){
  34. return beanMap.get(type);
  35. }
  36. Strategy strategy = this.applicationContext.getBean(strategyClass);
  37. beanMap.put(type, strategy);
  38. return strategy;
  39. }
  40. @Override
  41. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  42. this.applicationContext = applicationContext;
  43. }
  44. }

实用案例

在我们的平台中,有一部分是使用的netty框架编写的tcp服务,在服务端,需要将二进制转换为对象,在协议设计阶段,定义第一个字节表示对象类型,比如int,String等,第二三个字节,表示数据长度,后面的字节位传输内容。

比如,

0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09,解析出来的内容是int类型数字9。

0x02, 0x00, 0x03, 0x31, 0x32, 0x33, 解析出的内容是String类型,内容是 123。

在不使用策略模式的时候,需要将第一个字节解析出来,然会使用if--else判断类型,对后继的字节进行解析。

在实际的实现过程中,是使用了策略模式,并且使用注解的方式表示数据类型,实现过程如下。

定义策略接口和注解

定义 CodecStrategyType 注解和编码解码器的策略接口 CodecStrategy

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface CodecStrategyType {
  5. /**
  6. * 编码解码类型
  7. * @return
  8. */
  9. byte type();
  10. }
  11. public interface CodecStrategy<T> {
  12. T decoding(byte[] buffer);
  13. }
  14. /*
  15. * 通用解码接口
  16. */
  17. public interface Codec {
  18. Object decoding(byte[] bytes);
  19. }

策略实现

实现两种类型的解码器: Integer  和 String

  1. /**
  2. * integer解码
  3. */
  4. @CodecStrategyType(type = (byte)0x01)
  5. @Service
  6. public class IntgerCodecStrategy implements CodecStrategy<Integer> {
  7. @Override
  8. public Integer decoding(byte[] buffer) {
  9. int value;
  10. value = (int) ((buffer[3] & 0xFF)
  11. | ((buffer[2] & 0xFF)<<8)
  12. | ((buffer[1] & 0xFF)<<16)
  13. | ((buffer[0] & 0xFF)<<24));
  14. return value;
  15. }
  16. }
  17. @CodecStrategyType(type = (byte)0x02)
  18. @Service
  19. public class StringCodecStrategy implements CodecStrategy<String> {
  20. @Override
  21. public String decoding(byte[] bufferr) {
  22. return new String(bufferr);
  23. }
  24. }

策略上下文和策略注册

策略上下文类 CodecStrategyContext 提供了统一解码入口,将 byte[] 转换为 Object 类型,同时提供策略的注解接口 void registerStrategy(Byte type, Class<CodecStrategy<?>> strategyClass) ,注册解码类型对应的策略实现类。

策略上下文类同时还提供了策略Bean的创建,根据类型从Spring 的 ApplicationContext 获取策略bean,并缓存到map。

策略Bean处理类 CodecStrategyTypeBeanPostProcessor 中解析 CodecStrategyType 注解中指定的类型。


  1. @Component
  2. public class CodecStrategyContext implements ApplicationContextAware, Codec {
  3. private final Map<Byte, Class<CodecStrategy<?>>> strategyClassMap = new ConcurrentHashMap<>(64);
  4. private final Map<Byte, CodecStrategy<?>> beanMap = new ConcurrentHashMap<>(64);
  5. private ApplicationContext applicationContext;
  6. /**
  7. * 注册策略
  8. * @param type
  9. * @param strategyClass
  10. */
  11. public void registerStrategy(Byte type, Class<CodecStrategy<?>> strategyClass){
  12. if (strategyClassMap.containsKey(type)){
  13. throw new RuntimeException("strategy type:"+type+" exist");
  14. }
  15. strategyClassMap.put(type, strategyClass);
  16. }
  17. /**
  18. * 执行策略
  19. */
  20. @Override
  21. public Object decoding(byte[] bytes){
  22. Byte type = bytes[0];
  23. CodecStrategy<?> strategy =this.getStrategy(type);
  24. byte l1 = bytes[1];
  25. byte l2= bytes[2];
  26. short length = (short) ((l2 & 0xFF)
  27. | ((l1 & 0xFF)<<8));
  28. byte[] contentBytes = new byte[length];
  29. arraycopy(bytes,3,contentBytes,0, length);
  30. return strategy.decoding(contentBytes);
  31. }
  32. private CodecStrategy<?> getStrategy(Byte type) {
  33. Class<CodecStrategy<?>> strategyClass = strategyClassMap.get(type);
  34. return createOrGetStrategy(type, strategyClass);
  35. }
  36. private CodecStrategy<?> createOrGetStrategy(Byte type, Class<CodecStrategy<?>> strategyClass ){
  37. if (beanMap.containsKey(type)){
  38. return beanMap.get(type);
  39. }
  40. CodecStrategy<?> strategy = this.applicationContext.getBean(strategyClass);
  41. beanMap.put(type, strategy);
  42. return strategy;
  43. }
  44. @Override
  45. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  46. this.applicationContext = applicationContext;
  47. }
  48. }

  1. @Component
  2. public class CodecStrategyTypeBeanPostProcessor implements BeanPostProcessor, Ordered {
  3. private final Set<Class<?>> nonAnnotatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>(64));
  4. private final CodecStrategyContext strategyContext;
  5. private CodecStrategyTypeBeanPostProcessor(CodecStrategyContext context) {
  6. this.strategyContext = context;
  7. }
  8. @Override
  9. public int getOrder() {
  10. return LOWEST_PRECEDENCE;
  11. }
  12. @Override
  13. public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
  14. if (!this.nonAnnotatedClasses.contains(bean.getClass())) {
  15. // 获取使用 @StrategyDemo 注解的Class信息
  16. Class<?> targetClass = AopUtils.getTargetClass(bean);
  17. Class<CodecStrategy<?>> orderStrategyClass = (Class<CodecStrategy<?>>) targetClass;
  18. CodecStrategyType ann = findAnnotation(targetClass);
  19. if (ann != null) {
  20. processListener(ann, orderStrategyClass);
  21. }
  22. }
  23. return bean;
  24. }
  25. @Override
  26. public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  27. return bean;
  28. }
  29. protected void processListener(CodecStrategyType annotation,
  30. Class<CodecStrategy<?>> classes) {
  31. // 注册策略
  32. this.strategyContext
  33. .registerStrategy(annotation.type(), classes);
  34. }
  35. private CodecStrategyType findAnnotation(Class<?> clazz) {
  36. CodecStrategyType ann = AnnotatedElementUtils.findMergedAnnotation(clazz, CodecStrategyType.class);
  37. return ann;
  38. }
  39. }

使用和测试

测试Integer和String类型的策略:

  1. 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09,解析出来的内容是int类型数字9。
  2. 0x02, 0x00, 0x03, 0x31, 0x32, 0x33, 解析出的内容是String类型,内容是 123。

  1. @ExtendWith(SpringExtension.class)
  2. @ContextConfiguration(classes = {CodecStrategyTest.CodecStrategyTestConfig.class})
  3. public class CodecStrategyTest {
  4. @Resource
  5. Codec codec;
  6. @Test
  7. public void testInterDecoding(){
  8. byte[] buffer = new byte[]{
  9. 0x01,0x00, 0x04, 0x00, 0x00,0x00, 0x09
  10. };
  11. Integer decoding = (Integer)codec.decoding(buffer);
  12. assertThat(decoding)
  13. .isNotNull()
  14. .isEqualTo(9);
  15. }
  16. @Test
  17. public void testStringDecoding(){
  18. byte[] buffer = new byte[]{
  19. 0x02, 0x00, 0x03, 0x31, 0x32,0x33
  20. };
  21. String decoding = (String)codec.decoding(buffer);
  22. assertThat(decoding)
  23. .isNotNull()
  24. .isEqualTo("123");
  25. }
  26. @ComponentScan({"com.masterlink.strategy"})
  27. @Configuration
  28. public static class CodecStrategyTestConfig {
  29. }
  30. }

扩展复杂类型

自定义复杂类型User类,对应协议类型为 0xA0, 第2 、3 字节表示整个对象的字段长度,紧接着是 Integer 类型的age 和 String 类型的name,

比如 0xA0, 0x00 0x10 0x00, 0x04, 0x00, 0x00, 0x00, 0x17, 0x00, 0x08, 0x5A,0x68,0x61,0x6E,0x67,0x53, 0x61,0x6E, 对应的user对象是

  1. {
  2. "age": 23,
  3. "name": "ZhangSan"
  4. }
  1. @Data
  2. public class User {
  3. private Integer age;
  4. private String name;
  5. }

实现解码策略类

已知 User 中的基础类型依赖了 Integer 和 String ,所以在User的解码策略类中,依赖了 IntgerCodecStrategy 和 StringCodecStrategy


  1. @CodecStrategyType(type = (byte) (0xA0))
  2. @Service
  3. public class UserCodeStrategy implements CodecStrategy<User> {
  4. private final StringCodecStrategy stringCodecStrategy;
  5. private final IntgerCodecStrategy intgerCodecStrategy;
  6. public UserCodeStrategy(StringCodecStrategy stringCodecStrategy, IntgerCodecStrategy intgerCodecStrategy) {
  7. this.stringCodecStrategy = stringCodecStrategy;
  8. this.intgerCodecStrategy = intgerCodecStrategy;
  9. }
  10. @Override
  11. public User decoding(byte[] buffer) {
  12. byte ageL1 = buffer[0];
  13. byte ageL2 = buffer[1];
  14. short ageLength = (short) ((ageL2 & 0xFF)
  15. | ((ageL1 & 0xFF)<<8));
  16. byte[] ageBytes = new byte[ageLength];
  17. System.arraycopy(buffer,2, ageBytes,0,ageLength);
  18. byte nameL1 = buffer[0+ageLength];
  19. byte nameL2 = buffer[1+ageLength];
  20. short nameLength = (short) ((nameL2 & 0xFF)
  21. | ((nameL1 & 0xFF)<<8));
  22. byte[] nameBytes = new byte[nameLength];
  23. System.arraycopy(buffer,2+ageLength+2, nameBytes,0,nameLength);
  24. User user = new User();
  25. user.setAge(intgerCodecStrategy.decoding(ageBytes));
  26. user.setName(stringCodecStrategy.decoding(nameBytes));
  27. return user;
  28. }
  29. }

测试

通过测试可以发现很轻松的就扩展了一个复杂类型的解码算法,这样随着协议的增加,可以做到对修改代码关闭,对扩展代码开放,符合开闭原则。


  1. @Test
  2. public void testUserDecoding(){
  3. byte[] buffer = new byte[]{
  4. (byte)0xA0, (byte)0x00 ,(byte)0x10 ,(byte)0x00, (byte)0x04,
  5. (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x17, (byte)0x00,
  6. (byte)0x08, (byte)0x5A, (byte)0x68, (byte)0x61, (byte)0x6E,
  7. (byte)0x67, (byte)0x53, (byte)0x61, (byte)0x6E
  8. };
  9. User user = (User)codec.decoding(buffer);
  10. assertThat(user)
  11. .isNotNull();
  12. assertThat(user.getAge()).isEqualTo(23);
  13. assertThat(user.getName()).isEqualTo("ZhangSan");
  14. }

总结

  1. 使用策略模式,可以避免冗长的if-else 或 switch分支判断
  2. 掌握自定义注解的是使用方式
  3. 与使用 @Service("name") 注解相比,自定义注解方式支撑和扩展的类型或更灵活

关注我的公众号,一起探索新知识新技术

Spring 实现策略模式--自定义注解方式解耦if...else的更多相关文章

  1. 你可能使用了Spring最不推荐的注解方式

    前言 使用Spring框架最核心的两个功能就是IOC和AOP.IOC也就是控制反转,我们将类的实例化.依赖关系等都交由Spring来处理,以达到解耦合.利用复用.利于测试.设计出更优良程序的目的.而对 ...

  2. (转)Spring的bean管理(注解方式)

    http://blog.csdn.net/yerenyuan_pku/article/details/69663779 Spring的bean管理(注解方式) 注解:代码中的特殊标记,注解可以使用在类 ...

  3. Spring 的 Bean 管理(注解方式)

    Spring 的 Bean 管理(注解方式) 1. 导入必要的 jar 包和 xml 文件 使用注解需要导入 spring-aop 的 jar 包. applicationContext.xml 文件 ...

  4. spring AOP自定义注解方式实现日志管理

    今天继续实现AOP,到这里我个人认为是最灵活,可扩展的方式了,就拿日志管理来说,用Spring AOP 自定义注解形式实现日志管理.废话不多说,直接开始!!! 关于配置我还是的再说一遍. 在appli ...

  5. 基于Spring实现策略模式

    背景: 看多很多策略模式,总结下来实现原理大体都差不多,在这里主要是讲解下自己基于Spring更优雅的实现方案:这个方案主要是看了一些开源rpc和Spring相关源码后的一些思路,所以在此进行总结 首 ...

  6. 如何优雅地在 Spring Boot 中使用自定义注解,AOP 切面统一打印出入参日志 | 修订版

    欢迎关注个人微信公众号: 小哈学Java, 文末分享阿里 P8 资深架构师吐血总结的 <Java 核心知识整理&面试.pdf>资源链接!! 个人网站: https://www.ex ...

  7. springboot aop 自定义注解方式实现完善日志记录(完整源码)

    版权声明:本文为博主原创文章,欢迎转载,转载请注明作者.原文超链接 一:功能简介 本文主要记录如何使用aop切面的方式来实现日志记录功能. 主要记录的信息有: 操作人,方法名,参数,运行时间,操作类型 ...

  8. springboot aop 自定义注解方式实现一套完善的日志记录(完整源码)

    https://www.cnblogs.com/wenjunwei/p/9639909.html https://blog.csdn.net/tyrant_800/article/details/78 ...

  9. Spring知识点总结(三)之注解方式实现IOC和DI

        1. 注解概念        所谓注解就是给程序看的提示信息,很多时候都用来作为轻量级配置的方式.        关于注解的知识点,参看java基础课程中java基础加强部分的内容.    2 ...

随机推荐

  1. Java基础知识-简明阐述双亲委派机制及作用

    1.双亲委派机制及作用 1.1 什么是双亲委派机制 当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类 ...

  2. 微信小程序 | app.json配置属性

    app.json 文件用来对微信小程序进行全局配置,决定页面文件的路径.窗口表现.设置网络超时时间.设置多 tab 等. widows: 用于设置小程序的状态栏.导航条.标题.窗口背景色. navig ...

  3. time模块&datetime模块

    import time a=time.localtime(time.time()) #将时间戳转换为当前时区的元组 print(a) c=time.gmtime(time.time()) #把时间戳转 ...

  4. Hi3559AV100 NNIE开发(7) Ruyistudio 输出mobileface_func.wk与板载运行mobileface_chip.wk输出中间层数据对比

    前面随笔讲了关于NNIE的整个开发流程,并给出了Hi3559AV100 NNIE开发(5)mobilefacenet.wk仿真成功量化及与CNN_convert_bin_and_print_featu ...

  5. go的令牌桶实现库 go-rate

    关于我 我的博客|文章首发 go-rate是速率限制器库,基于 Token Bucket(令牌桶)算法实现. go-rate被用在LangTrend的生产中 用于遵守GitHub API速率限制. 速 ...

  6. String 的不可变真的是因为 final 吗?

    尽人事,听天命.博主东南大学硕士在读,热爱健身和篮球,乐于分享技术相关的所见所得,关注公众号 @ 飞天小牛肉,第一时间获取文章更新,成长的路上我们一起进步 本文已收录于 「CS-Wiki」Gitee ...

  7. [状压DP]车

    车 车 车 题目描述 在 n ∗ n n*n n∗n( n ≤ 20 n≤20 n≤20)的方格棋盘上放置 n n n个车(可以攻击所在行.列),有些格子不能放,求使它们不能互相攻击的方案总数. 输入 ...

  8. HTML5与CSS3新增特性笔记

    HTML5 HTML5和HTML事件 注意:行内代码的为H5新增事件 Window事件属性: 针对 window 对象触发的事件(应用到 标签) onafterprint 文档打印之后运行的脚本 on ...

  9. JDK8之后,在java语言这条路怎么走?

    前言 自2017年9月以来,Oracle按照免费的开源许可证(类似于Linux的许可证)提供JDK版本 .从Java SE 11(2018年9月,LTS)开始,Oracle不仅为开源许可下的所有用户免 ...

  10. 如何把 Caffeine Cache 用得如丝般顺滑?

    一.关于 Caffeine Cache 在推荐服务中,虽然允许少量请求因计算超时等原因返回默认列表.但从运营指标来说,越高的"完算率"意味着越完整的算法效果呈现,也意味着越高的商业 ...