Java设计模式之3种策略模式实现

一、什么是策略模式   所谓策略模式,就是定义了一组策略,分别封装在不同类中,每种策略都可以根据当前场景相互替换,从而使策略的变化可以独立于操作者。比如我们要去某个地方,会根据距离的不同来选择不同的出行方式,比如:共享单车、打车、坐飞机等等,这些出行方式即不同的策略。 二

一、什么是策略模式

  所谓策略模式,就是定义了一组策略,分别封装在不同类中,每种策略都可以根据当前场景相互替换,从而使策略的变化可以独立于操作者。比如我们要去某个地方,会根据距离的不同来选择不同的出行方式,比如:共享单车、打车、坐飞机等等,这些出行方式即不同的策略。

二、为什么使用策略模式

  我们在平时为了快速完成业务需求,会在代码实现时大量使用if…else,但当代码中大量充实着这种结构时会使代码的可读性、维护性降低并更容易出错。策略模式是解决这种问题的好办法,它符合设计模式中的开闭原则。

三、策略模式的组成

3.1 结构

image.png

3.2 角色

  • Context(环境类)
    策略上下文,维护一个Strategy对象的引用,屏蔽上层模块对策略、算法的直接访问
  • Strategy(抽象策略类)
    这是一个抽象角色,定义了所有的具体策略类所需的接口
  • ConcreteStrategy(具体策略类)
    实现了在抽象策略类中定义的方法,封装了相关的算法或行为。根据多态的特性,在运行时,具体策略类将覆盖在环境类中定义的抽象策略累对象

四、如何实现策略模式

源代码:https://github.com/mofan3/strategy-mode.git

4.1 基于注解实现

  • 枚举定义

/**
 * 消息类型枚举
 */
@Getter
@AllArgsConstructor
public enum MessageType {
    PUSH("通知栏"),
    SMS("短信"),
    EMAIL( "邮件"),
    WECHAT( "微信"),
    DING_DING_ROBOT( "钉钉机器人"),
    DING_DING_WORK_NOTICE("钉钉工作通知")
    ;

    /**
     * 描述
     */
    private String description;
}
  • 注解定义
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface HandlerAnnotation {  
    MessageType type();
}
  • 策略接口定义
public interface BizHandler {
    void execute(MessageInfo messageInfo);
}
  • 策略实现
@HandlerAnnotation(type = MessageType.SMS)
@Slf4j
public class SmsHandler implements BizHandler {

    @Override
    public void execute(MessageInfo messageInfo) {
        SmsMessageInfo smsMessageInfo = (SmsMessageInfo) messageInfo;
        // 发送短信
        log.info("发送短信消息:{}",smsMessageInfo.getContent());
    }

}
@HandlerAnnotation(type = MessageType.WECHAT)
@Slf4j
public class WeChatHandler implements BizHandler {

    @Override
    public void execute(MessageInfo messageInfo) {
        WeChatMessageInfo weChatMessageInfo = (WeChatMessageInfo) messageInfo;
        // 发送微信
        log.info("发送微信消息:{}",weChatMessageInfo.getContent());
    }

}
  • 策略组装
@Component
public class BizHandlerContext  implements BeanPostProcessor {

    private final Map<String, BizHandler> bizHandlerMap = new HashMap<>();

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        HandlerAnnotation annotation = AnnotationUtils.findAnnotation(bean.getClass(), HandlerAnnotation.class);
        if (ClassUtils.isAssignableValue(BizHandler.class,bean) && annotation != null) {
            bizHandlerMap.put(annotation.type().name(), (BizHandler)bean);
        }
        return bean;
    }

    public BizHandler getBizHanler(String type) {
        BizHandler bizHandler = this.bizHandlerMap.get(type);
        if (bizHandler == null) {
            throw new RuntimeException("not.found.BizHandler");
        }
        return bizHandler;
    }

}

BizHandlerContext类实现BeanPostProcessor接口,在postProcessBeforeInitialization(…)方法中拿到所有含有@HandlerAnnotation注解的bean,以bean的注解中的type值作为键和bean作为值构建handlerMap,从而在外界以type作为请求时可以准确的获取到handlerMap中相应的bean。

  • 测试
@SpringBootTest
class StrategyModeApplicationTests {

    @Autowired
    BizHandlerContext bizHandlerContext;

    @Test
    void contextLoads() {
        AbstractBizHandler bizHanler = bizHandlerContext.getBizHanler(MessageType.SMS.name());
        SmsMessageInfo smsContentModel = new SmsMessageInfo();
        smsContentModel.setContent("【XXX】验证码:313122,您正在进行登录操作,若非本人操作,请勿泄露");
        bizHanler.execute(smsContentModel);

    }

}

4.2 基于@Autowired的实现

  • 策略接口定义
public interface BizHandler {

    void execute(MessageInfo messageInfo);

    String getType();
}
  • 策略实现
@Service
@Slf4j
public class SmsHandler implements BizHandler {

    @Override
    public void execute(MessageInfo messageInfo) {
        SmsMessageInfo smsMessageInfo = (SmsMessageInfo) messageInfo;
        // 发送短信
        log.info("发送短信消息:{}",smsMessageInfo.getContent());
    }

    @Override
    public String getType() {
        return MessageType.SMS.name();
    }

}
@Service
@Slf4j
public class WeChatHandler implements BizHandler {

    @Override
    public void execute(MessageInfo messageInfo) {
        WeChatMessageInfo weChatMessageInfo = (WeChatMessageInfo) messageInfo;
        // 发送微信
        log.info("发送微信消息:{}",weChatMessageInfo.getContent());
    }

    @Override
    public String getType() {
        return MessageType.WECHAT.name();
    }

}
  • 策略组装
@Component
public class BizHandlerContext{

    private final Map<String, BizHandler> bizHandlerMap = new HashMap<>();

    @Autowired(required = false)
    public void putBizHandler(List<BizHandler> bizHandlerList) {
        for (BizHandler bizHandler : bizHandlerList) {
            String type = bizHandler.getType();
            if (StringUtils.isEmpty(type)) {
                throw new IllegalArgumentException("BizHandler name must not be empty");
            }
            bizHandlerMap.put(type, bizHandler);
        }
    }

    public BizHandler getBizHanler(String type) {
        BizHandler bizHandler = this.bizHandlerMap.get(type);
        if (bizHandler == null) {
            throw new RuntimeException("not.found.BizHandler");
        }
        return bizHandler;
    }
}

BizHandlerContext使用了Spring的@Autowired注解按照类型自动装配的功能,循环遍历BizHandler集合,获取type值作为键和bean作为值加入到handlerMap中。

  • 测试
@SpringBootTest
class StrategyModeApplicationTests {

    @Autowired
    BizHandlerContext bizHandlerContext;

    @Test
    void contextLoads() {
        AbstractBizHandler bizHanler = bizHandlerContext.getBizHanler(MessageType.SMS.name());
        SmsMessageInfo smsContentModel = new SmsMessageInfo();
        smsContentModel.setContent("【XXX】验证码:313122,您正在进行登录操作,若非本人操作,请勿泄露");
        bizHanler.execute(smsContentModel);

    }

}

4.3 基于InitializingBean的实现

  • 抽象处理器定义
public abstract class AbstractBizHandler implements InitializingBean {

    @Autowired
    private BizHandlerContext bizHandlerContext;

    @Override
    public void afterPropertiesSet() throws Exception {
        bizHandlerContext.putBizHandler(getType(),this);
    }

    public abstract void execute(MessageInfo messageInfo);

    public abstract String getType();
}

InitializingBean接口为bean提供了属性初始化后的处理方法,它只包括afterPropertiesSet方法。也可用@PostConstruct注解实现,但接口实现方式在调用效率上高一些,@PostConstruct是通过反射机制调用的

  • 策略实现
@Service
@Slf4j
public class SmsHandler extends AbstractBizHandler {

    @Override
    public void execute(MessageInfo messageInfo) {
        SmsMessageInfo smsMessageInfo = (SmsMessageInfo) messageInfo;
        // 发送短信
        log.info("发送短信消息:{}",smsMessageInfo.getContent());
    }

    @Override
    public String getType() {
        return MessageType.SMS.name();
    }

}
@Service
@Slf4j
public class WeChatHandler extends AbstractBizHandler {

    @Override
    public void execute(MessageInfo messageInfo) {
        WeChatMessageInfo weChatMessageInfo = (WeChatMessageInfo) messageInfo;
        // 发送微信
        log.info("发送微信消息:{}",weChatMessageInfo.getContent());
    }

    @Override
    public String getType() {
        return MessageType.WECHAT.name();
    }

}
  • 策略组装
@Component
public class BizHandlerContext{

    private final Map<String, AbstractBizHandler> bizHandlerMap = new HashMap<>();

    public void putBizHandler(String type, AbstractBizHandler handler) {
        bizHandlerMap.put(type, handler);
    }

    public AbstractBizHandler getBizHanler(String type) {
        AbstractBizHandler bizHandler = this.bizHandlerMap.get(type);
        if (bizHandler == null) {
            throw new RuntimeException("not.found.BizHandler");
        }	
        return bizHandler;
    }
}
  • 测试
@SpringBootTest
class StrategyModeApplicationTests {

    @Autowired
    BizHandlerContext bizHandlerContext;

    @Test
    void contextLoads() {
        AbstractBizHandler bizHanler = bizHandlerContext.getBizHanler(MessageType.SMS.name());
        SmsMessageInfo smsContentModel = new SmsMessageInfo();
        smsContentModel.setContent("【XXX】验证码:313122,您正在进行登录操作,若非本人操作,请勿泄露");
        bizHanler.execute(smsContentModel);

    }

}

五、总结

  通过策略设计模式的使⽤可以把我们方法中的if语句优化掉,以上3种实现方式本质上是利用Map的key-value结构做映射来解决⼤量if else 的叠加嵌套问题,⼤量的if语句使用会让代码难以扩展,也不好维护,同时在后期遇到各种问题也很难维护。在使用这样的设计模式后可以很好的满⾜隔离性与和扩展性,对于不断新增的需求也⾮常⽅便承接。

知秋君
上一篇 2024-09-11 16:48
下一篇 2024-09-11 16:12

相关推荐