一、引言

在软件开发中,策略模式是一种常用的设计模式,它允许在运行时选择算法的行为。本文将详细介绍策略模式在 Java 中的实现,从最基础的版本逐步演进到最终的高效优化版本,旨在解决策略选择、排序以及避免代码中大量 if-else 语句等问题。

二 第一版 基础策略模式实现

在策略模式的第一版中,我们按照传统的方式实现,即定义接口、实现接口、将实现类注入容器,然后通过循环调用不同的策略。

接口定义

接口定义了策略类需要实现的方法,为后续的策略实现提供了统一的规范。

主程序

主程序负责调用不同的策略,通过循环遍历策略列表,找到合适的策略并执行。

三 第二版 新增兜底策略及排序问题解决

随着业务的发展,我们需要新增兜底策略,但为了防止兜底策略截断后续的策略实现,我们需要对策略类进行排序。

问题分析

兜底策略可能会在不恰当的时候被执行,从而影响其他策略的正常调用。

解决方案

我们可以给其他策略类添加 @Order 注解,这个注解可以控制 Bean 的生成顺序,从而保证策略类按照我们期望的顺序执行。

所以 我们可以给其他策略类 加 order 注解 这个注解可以控制 bean 的生成顺序

四 第三版:将策略选择交给主流程

为了更好地管理策略的选择,我们将策略的选择逻辑从具体的策略类中分离出来,交给主流程处理。我们使用一个枚举来表示不同的用户类型,并在主流程中编写一个方法来返回对应的策略。

枚举定义
public enum userType {
    NORMAL,
    SMALL,
    BIG,
    SUPER,
    PERSONAL
}
接口定义
public interface CustomerService {

    userType getUserType();
    //    校验


    String findCustomer();
}
策略实现类
@Component
@Order(1)
public class SuperCustomerService implements CustomerService {
    @Override
    public userType getUserType() {
        return userType.SUPER;
    }

    @Override
    public String findCustomer() {
        return null;
    }
}
控制器类
@RestController
public class stratgeyController {

    @Autowired
    List<CustomerService> customerServiceList;

    @RequestMapping("customer/{recharge}")
    public String customer(@PathVariable("recharge") int recharge){
        for (CustomerService customerService : customerServiceList) {
            if (customerService.getUserType().equals(analysisUserType(recharge))) {
                return customerService.findCustomer();
            }
        }
        throw new IllegalArgumentException("找不到客服");

    }

    private userType analysisUserType(int recharge){
        if(recharge>0 && recharge<=100){
            return userType.NORMAL;
        }
        if(recharge>100 && recharge<=1000){
            return userType.SMALL;
        }

        if(recharge>1000 && recharge<=10000){
            return userType.BIG;
        }
        if(recharge>10000 && recharge<=100000){
            return userType.SUPER;
        }
        if(recharge>100000 && recharge<=1000000){
            return userType.PERSONAL;
        }
        return null;
    }
}
缺点分析

缺点是 主方法依旧存在了大量的 if-else

五、第四版:在枚举中做循环

为了避免主方法中大量的 if-else 语句,我们在枚举中实现一个方法,该方法接收充值金额参数 recharge,通过遍历枚举的所有成员,对每个成员使用 Lambda 表达式判断其是否满足充值金额条件,若满足则返回对应的枚举值。

枚举优化
public enum UserType {
    NORMAL(recharge-> recharge>0 && recharge<=100),
    SMALL(recharge-> recharge>100 && recharge<=1000),
    BIG(recharge-> recharge>1000 && recharge<=10000),
    SUPER(recharge-> recharge>10000 && recharge<=100000),
    PERSONAL(recharge-> recharge>100000 && recharge<=1000000);

    private  final IntPredicate support;
    UserType(IntPredicate support){
        this.support=support;
    }

    public static UserType typeOf(int recharge){
        for (UserType value : UserType.values()) {
            if(value.support.test(recharge)){
                return value;
            }
        }
        return null;
    }

}

在枚举中实现一个方法,该方法接收充值金额参数 recharge,通过遍历枚举的所有成员,对每个成员使用 Lambda 表达式判断其是否满足充值金额条件(如 recharge 落在该成员对应的金额区间内),若满足则返回对应的枚举值。

后续就可以移除 analysisUserType 在主流程直接根据 recharge 返回枚举 然后遍历策略类 判断策略类的枚举类型是否和参数对应的枚举类型相等就可以

新问题分析

现在的问题是 如果我的策略类有一百个 那我每次不都要遍历一百次了么???

六、第五版:使用 Map 替换 List

为了提高策略选择的效率,我们使用一个 Map 来存储枚举以及枚举对应的策略类。使用构造注入法,在项目启动时遍历一次策略类列表,将 Map 进行赋值,后续可以直接通过枚举从 Map 中获取对应的策略类。

@RestController
public class stratgeyController {

   

    @Autowired
    DefaultCustomerService defaultCustomerService;

    Map<UserType,CustomerService> map;

    @RequestMapping("customer/{recharge}")
    public String customer(@PathVariable("recharge") int recharge){
        //        通过充值金额判断用户类型
        UserType userType = UserType.typeOf(recharge);
        CustomerService customerService = map.getOrDefault(userType,defaultCustomerService);
        return customerService.findCustomer();
    }



    @Autowired
    public void setCustomerMap(List<CustomerService> customerServiceList){
        map= customerServiceList
        .stream()
        .filter(customerService -> customerService.getUserType()!=null)
        .collect(Collectors.toMap( CustomerService::getUserType, Function.identity()));

    }

这里主要是 用一个 map 来存储枚举 以及枚举对应的策略类

使用构造注入法 这样只要项目启动的时候 遍历一次就行 然后就可以把 map 进行赋值

map 中 key 就是枚举 value 就是对应的策略类.

七、最终版:使用自定义注解优化接口

因为我们接口中 还是有一个方法 来获取策略类对应的枚举 如果只想要策略接口足够干净 只留一个处理方法 那么可以用到自定义注解 给每个策略实现类的注解 value 换成对应的枚举 这样在填充 map 时候 获取特定注解的 value 就可以了 这样可以让策略接口只需要有处理方法 就行

自定义注解定义
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SupportUserType {
    UserType value();
}
策略实现类
@Component
@SupportUserType(UserType.BIG)
public class BigCustomerSercice implements CustomerService {



    @Override
    public String findCustomer() {
        return "大r客服";
    }
}
控制器类
@RestController
public class stratgeyController {



    @Autowired
    DefaultCustomerService defaultCustomerService;

    Map<UserType,CustomerService> map;

    @RequestMapping("customer/{recharge}")
    public String customer(@PathVariable("recharge") int recharge){
        //        通过充值金额判断用户类型
        UserType userType = UserType.typeOf(recharge);
        CustomerService customerService = map.getOrDefault(userType,defaultCustomerService);
        return customerService.findCustomer();
    }


    @Autowired
    public void setCustomerMap(List<CustomerService> customerServiceList){
        map= customerServiceList
        .stream()
        .filter(customerService -> customerService.getClass().isAnnotationPresent(SupportUserType.class))
        .collect(Collectors.toMap(this::getCustomerServiceUserTypeFunction, Function.identity()));
        if(this.map.size()!=UserType.values().length){
            throw new IllegalArgumentException("有策略类没有注入进来");
        }

    }

    //    获取注解value
    private UserType getCustomerServiceUserTypeFunction(CustomerService customerService) {
        return customerService.getClass().getAnnotation(SupportUserType.class).value();
    }


}

八、总结

通过不断地优化和改进,我们从基础的策略模式实现逐步演进到最终的高效优化版本。使用枚举、Map 和自定义注解等技术,解决了策略选择、排序以及代码可维护性等问题,提高了系统的性能和可扩展性。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐