教你如何使用策略模式

策略模式

在策略模式中一个类的行为或者其算法在运行是可以进行改变,这种的类型也可以叫做行为型模式。

基本结构

  • 策略对象类(表示行为)
  • 策略父类

优缺点

优点

  • 将多个行为进行封装,能够根据配置进行替换。
  • 能够省略较多的判断语句 if...else...,便于维护。
  • 实现一个公共接口。
  • 符合开发的开闭原则。

缺点

  • 策略类会很多,一个行为一个策略类。
  • 所有的策略类都需要对外暴漏。

场景

  • 在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。

  • 一个系统需要动态地在几种算法中选择一种。

  • 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

实例场景

今天和朋友要去公园玩儿,那么就要选择出行方式,那么创建一个出行的接口,创建自行车、公交、步行等行为类,并且都继承出行的接口,再创建我的选择类(Context)来选择出行方式。至此大体完成。

代码

出行方式接口

/**
 * @author Surpass
 * @Package com.hkrt.demo
 * @Description: 出行方式(工具)
 * @date 2021/1/15 16:16
 */
public interface Strategy {

    void goOutTool();
}

出行方式实现类

公交车

/**
 * @author Surpass
 * @Package com.hkrt.demo.strategy
 * @Description: 公交车
 * @date 2021/1/15 16:20
 */
public class BusWayStrategy implements Strategy{

    @Override
    public void goOutTool() {
        System.out.println("今天坐公交车出去玩儿");
    }
}

步行

/**
 * @author Surpass
 * @Package com.hkrt.demo.strategy
 * @Description: 步行方式
 * @date 2021/1/15 16:19
 */
public class WalkWayStrategy implements Strategy {

    @Override
    public void goOutTool() {
        System.out.println("今天要步行出去玩儿");
    }
}

自己开车

/**
 * @author Surpass
 * @Package com.hkrt.demo.strategy
 * @Description: 开车
 * @date 2021/1/15 16:21
 */
public class CarWayStrategy implements Strategy {

    @Override
    public void goOutTool() {
        System.out.println("今天自己开车出去玩儿");
    }
}

出行方式上下文

/**
 * @author Surpass
 * @Package com.hkrt.demo.strategy
 * @Description: 选择行为(上下文)
 * @date 2021/1/15 16:22
 */
public class StrategyContext {

    private Strategy strategy;

    public StrategyContext(Strategy strategy) {
        this.strategy = strategy;
    }

    public void goOut(){
        strategy.goOutTool();
    }
}

测试类

/**
 * @author Surpass
 * @Package com.hkrt.demo.strategy
 * @Description: 我和朋友准备出去玩儿
 * @date 2021/1/15 16:24
 */
public class PlayTest {
    public static void main(String[] args) {

        /**
         * 选择步行出去玩儿
         */
        StrategyContext walkWayStrategy = new StrategyContext(new WalkWayStrategy());
        walkWayStrategy.goOutTool();

        /**
         * 选择坐公交出去玩儿
         */
        StrategyContext busWayStrategy = new StrategyContext(new BusWayStrategy());
        busWayStrategy.goOutTool();

        /**
         * 选择自己开车出去玩儿
         */
        StrategyContext carWayStrategy = new StrategyContext(new CarWayStrategy());
        carWayStrategy.goOutTool();

    }
}

执行结果

Connected to the target VM, address: '127.0.0.1:11631', transport: 'socket'
今天要步行出去玩儿
今天坐公交车出去玩儿
今天自己开车出去玩儿
Disconnected from the target VM, address: '127.0.0.1:11631', transport: 'socket'

这样一个简单的策略就是出来了,如果出现新增的策略,只需要实现公共的出行方式接口,然后写自己的逻辑就OK了。

SpringBoot中应用

接了个需求,对接第三方接口进行数据的校验,每一个第三方都是一套规范,考虑到后续的可扩展性,和内部的统一,使用策略类进行实现。

  • 创建一个配置菜单对接入的第三方进行记录(标志 channelCode)。
  • 创建校验接口。
  • 创建第三方接口类实现校验接口,类中写校验逻辑代码。

在这里我去掉了Context上下文,直接在逻辑代码中注入校验接口,根据配置中的第三方数据的 channelCode 判断走哪个第三方接口( channelCode的值就是该第三方接口类在Spring中的beanName)。

校验接口

public interface BusinessAuthChannelService {

    /**
     *   企业信息要素验证
     * @param dto 校验数据
     * @return java.util.Map<java.lang.String , java.lang.Object>
     * @throws
     * @author Surpass
     * @date 2021/1/7 15:56
     */
    Map<String, Object> auth(AuthChannelDto dto);
}

第三方校验实现类(业务代码不便展示)

@Slf4j
@Service("qcc")
public class QccAuthChannelServiceImpl implements BusinessAuthChannelService {

    private Gson gson = new Gson();

    /**
     *  鉴权查询
     * @param dto
     * @return java.util.Map<java.lang.String , java.lang.Object>
     * @throws
     * @author Surpass
     * @date 2021/1/11 13:51
     */
    @Override
    public Map<String, Object> auth(AuthChannelDto dto) {
        Map<String, Object> resultMap = new HashMap<>();
        return resultMap;
    }
}

校验接口控制层

@Slf4j
@RestController
@RequestMapping("/businessAuthChannel")
public class BusinessAuthChannelController {

    @Autowired
    private BusinessAuthChannelConfigService businessAuthChannelConfigService;

    @Autowired
    private BusinessAuthChannelOrderService businessAuthChannelOrderService;

    private Gson gson = new Gson();

    /**
     *   企业信息要素验证
     * @param dto
     * @return java.util.Map<java.lang.String , java.lang.Object>
     * @throws
     * @author Surpass
     * @date 2021/1/7 15:38
     */
    @RequestMapping("/auth")
    public Map<String,Object> auth(@RequestBody AuthChannelDto dto){
        log.info("企业信息要素验证,要素鉴权参数:"+gson.toJson(dto));
        Map<String, Object> map = new HashMap<>(2);
        Map<String, Object> authResult = null;
        //校验参数
        boolean validateParameter = this.validateParameter(dto);
        if (!validateParameter){
            map.put("rspCode", "99");
            map.put("rspMsg", "参数校验失败");
            log.info("企业信息要素验证,返回数据:"+map);
            return map;
        }

        //查询鉴权通道
        BusinessAuthChannelConfig config = this.getAuthChannelConfig(dto);
        //判断能够鉴权的标识
        boolean flag = false;
        if (config != null){
            //是否存在该通道编码的调用service实现类(spring bean中存在通道编码的bean)
            boolean existBean = SpringUtil.getApplicationContext().containsBean(config.getChannelCode());
            if (existBean){
                flag = true;
            }
        }
        if (!flag){
            map.put("rspCode", "99");
            map.put("rspMsg", "找不到可用通道");
            log.info("企业信息要素验证,返回数据:"+map);
            this.saveBusinessAuthChannelOrder(dto, authResult, config);
            return map;
        }

        //调用鉴权通道
        BusinessAuthChannelService authChannelService =
                (BusinessAuthChannelService)SpringUtil.getBean(config.getChannelCode());
        authResult = authChannelService.auth(dto);

        //保存信息记录
        this.saveBusinessAuthChannelOrder(dto, authResult, config);
        Map<String, Object> resultMap = (Map<String, Object>)authResult.get("resultMap");
        log.info("企业信息要素验证,返回数据:"+resultMap);
        return resultMap;
    }

    /**
     *   鉴权要素参数校验
     * @param dto
     * @return boolean
     * @throws
     * @author Surpass
     * @date 2021/1/12 15:32
     */
    private boolean validateParameter(AuthChannelDto dto){
    }

    /**
     *   根据参数条件查询可用的鉴权通道
     * @param dto
     * @return com.p4.risk.entity.BusinessAuthChannelConfig
     * @throws
     * @author Surpass
     * @date 2021/1/12 15:32
     */
    private BusinessAuthChannelConfig getAuthChannelConfig(AuthChannelDto dto){
        //根据参数在配置表中查询第三方接口配置信息
    }

    /**
     *   保存操作记录
     * @param dto 企业信息鉴权信息
     * @param authResult 鉴权结果
     * @param config 企业鉴权通道配置
     * @return void
     * @throws
     * @author Surpass
     * @date 2021/1/12 15:33
     */
    private void saveBusinessAuthChannelOrder(AuthChannelDto dto, Map<String, Object> authResult,
                                              BusinessAuthChannelConfig config){
       
    }
}

解释一下:主要是 auth 这个方法时主要流程,根据参数查询出来的第三方接口配置字段的 channelCode ,使用SpringUtil在Spring中查找是否存在有该值的beanName,如果不存在,则直接保存操作记录后返回。如果存在则使用 SpringUtil.getBean(channelCode)获得该编码的bean,然后调用 authResult = authChannelService.auth(dto) 就进入到第三方校验实现类中进行校验,校验完毕后保存操作记录,流程结束。

尾言

代码不全,但注释应该足够了,如果对上述代码有疑问的烦请留言,如果各位喜欢这篇文章,劳烦各位伸出小手点个赞,收个藏,关个注。

推荐阅读更多精彩内容