优雅重构:使用自定义注解和策略模式实现动态查询功能

156 阅读5分钟

大家好,欢迎来到风筝的设计模式专栏,我相信快节奏的生活下,文字的力量依然强大!

很久没有更新这个专栏了,今天在开发过程中用策略模式重构了一个根据类别查询的功能。带大家来实战一下!

引言

在软件开发中,经常会遇到根据不同的条件执行不同的逻辑的场景。对于这种情况,我们通常会使用if-else语句或switch语句来实现条件分支。然而,随着条件逻辑的增加,代码会变得越来越冗长和难以维护。为了解决这个问题,我们可以使用自定义注解和策略模式,使代码更加优雅和灵活。本篇博客将介绍如何重构一个分类查询接口,使用自定义注解和策略模式来实现动态查询功能。

问题背景

我们的应用中有一个兑换功能,有多种类型的兑换方式,例如:黄金、票券、积分等。现在需要为每种类型的兑换方式提供一个分类查询接口。在之前的实现中,使用了if-else语句判断不同类型,然后分别查询相应的数据并封装成返回结果。随着类型的增加,代码变得越来越臃肿,维护起来十分困难。、 原型如下:

image.png

原代码

if ("黄金".equals(type)) {  
UserDepositGold gold = new UserDepositGold();  
List<UserDepositGold> goldList = userDepositGoldService.selectUserDepositGoldList(gold);  
List<AjaxResult> resultList = new ArrayList<>();  
// 将 UserDepositGold 数据转换为 AjaxResult  
for (UserDepositGold depositGold : goldList) {  
AjaxResult ajaxResult = new AjaxResult();  
ajaxResult.put("type", "gold");  
ajaxResult.put("couponId", depositGold.getGoldId());  
ajaxResult.put("couponName", depositGold.getGoldName());  
ajaxResult.put("couponImg", depositGold.getGoldImg());  
ajaxResult.put("couponTotal", depositGold.getGoldTotal());  
ajaxResult.put("couponExplain", depositGold.getGoldExplain());  
ajaxResult.put("createTime", depositGold.getCreateTime());  
resultList.add(ajaxResult);  
}  
return resultList;  
} else if ("票券".equals(type)) {  
UserDepositCoupon coupon = new UserDepositCoupon();  
List<UserDepositCoupon> couponList = userDepositCouponService.selectUserDepositCouponList(coupon);  
List<AjaxResult> resultList = new ArrayList<>();  
// 将 UserDepositCoupon 数据转换为 AjaxResult  
for (UserDepositCoupon depositCoupon : couponList) {  
AjaxResult ajaxResult = new AjaxResult();  
ajaxResult.put("type", "coupon");  
ajaxResult.put("couponId", depositCoupon.getCouponId());  
ajaxResult.put("couponName", depositCoupon.getCouponName());  
ajaxResult.put("couponImg", depositCoupon.getCouponImg());  
ajaxResult.put("couponTotal", depositCoupon.getCouponTotal());  
ajaxResult.put("couponExplain", depositCoupon.getCouponExplain());  
ajaxResult.put("createTime", depositCoupon.getCreateTime());  
// 其他属性根据需要添加  
resultList.add(ajaxResult);  
}  
return resultList;  
} else {  
return null;  
}

优雅重构

为了解决上述问题,我们决定使用自定义注解和策略模式来重构这个接口,使其更加优雅和灵活。

步骤一:定义自定义注解

我们首先定义一个自定义注解@StrategyType,用于标记查询类型。这样,每个兑换类型的查询策略都可以使用该注解标记自己的类型。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface StrategyType {
    String value();
}

步骤二:创建查询策略接口

接下来,我们创建一个查询策略接口DepositQueryStrategy,使用泛型来实现灵活的对象封装。该接口定义了一个getDepositList()方法,用于查询数据并封装成返回结果。

public interface DepositQueryStrategy<T> {
    List<AjaxResult> getDepositList();
}

步骤三:创建黄金兑换查询策略

现在,我们为黄金兑换类型创建一个查询策略类GoldDepositQueryStrategy,并使用@StrategyType注解标记其类型为"gold"。

@Service
@StrategyType("gold")
public class GoldDepositQueryStrategy implements DepositQueryStrategy<UserDepositGold> {
    // ... 黄金兑换查询逻辑 ...
}

步骤四:创建票券兑换查询策略

然后,我们为票券兑换类型创建一个查询策略类CouponDepositQueryStrategy,并使用@StrategyType注解标记其类型为"coupon"。

@Service
@StrategyType("coupon")
public class CouponDepositQueryStrategy implements DepositQueryStrategy<UserDepositCoupon> {
    // ... 票券兑换查询逻辑 ...
}

步骤五:创建策略工厂类

接着,我们创建一个策略工厂类DepositQueryStrategyFactory,用于根据类型获取相应的查询策略。

@Service
public class DepositQueryStrategyFactory {
    private final Map<String, DepositQueryStrategy<?>> depositQueryStrategyMap;

    @Autowired
    public DepositQueryStrategyFactory(List<DepositQueryStrategy<?>> depositQueryStrategies) {
        depositQueryStrategyMap = new HashMap<>();
        for (DepositQueryStrategy<?> strategy : depositQueryStrategies) {
            String type = getTypeFromStrategy(strategy);
            depositQueryStrategyMap.put(type, strategy);
        }
    }

    private String getTypeFromStrategy(DepositQueryStrategy<?> strategy) {
        StrategyType strategyType = strategy.getClass().getAnnotation(StrategyType.class);
        return strategyType != null ? strategyType.value() : null;
    }

    public DepositQueryStrategy<?> getQueryStrategyByType(String type) {
        return depositQueryStrategyMap.get(type);
    }
}

步骤六:更新控制器

最后,在控制器中使用策略工厂和自定义注解,实现动态查询功能。

@RestController
public class DepositListController {

    private final DepositQueryStrategyFactory depositQueryStrategyFactory;

    @Autowired
    public DepositListController(DepositQueryStrategyFactory depositQueryStrategyFactory) {
        this.depositQueryStrategyFactory = depositQueryStrategyFactory;
    }

    @GetMapping("/getDepositList")
    public List<AjaxResult> getDepositList(@RequestParam(value = "pageNum", required = true) Integer pageNum,
                                           @RequestParam(value = "pageSize", required = true) Integer pageSize,
                                           @RequestParam(value = "type", required = true) String type) {
        PageHelper.startPage(pageNum, pageSize);
        // 按照 createTime 降序排序
        PageHelper.orderBy("create_time desc");

        DepositQueryStrategy<?> depositQueryStrategy = depositQueryStrategyFactory.getQueryStrategyByType(type);
        if (depositQueryStrategy == null) {
            throw new IllegalArgumentException("无效的类型: " + type);
        }

        List<?> depositList = depositQueryStrategy.getDepositList();
        List<AjaxResult> resultList = new ArrayList<>();

        // 将查询结果对象封装成 AjaxResult
        for (Object deposit : depositList) {
            AjaxResult ajaxResult = new AjaxResult();
            // 根据类型处理不同属性,这里只需要将属性名放入 ajaxResult 即可
            ajaxResult.put("type", type);
            ajaxResult.put("property1", deposit.getProperty1());
            ajaxResult.put("property2", deposit.getProperty2());
            // 添加其他属性,如需添加其他属性,可在 DepositQueryStrategy 中定义相应的方法并实现
            resultList.add(ajaxResult);
        }

        return resultList;
    }
}

话不多说,看效果:

image.png

缺点

尽管使用自定义注解和策略模式可以实现代码的优雅重构和动态查询功能,但这种模式也有一些潜在的缺点,需要在使用时考虑到:

  1. 增加类的数量: 使用策略模式会导致增加更多的类和接口,特别是针对每种不同类型的策略都需要创建一个具体的实现类,可能会使类的数量增加。
  2. 可能引起过度设计: 对于简单的应用场景,使用自定义注解和策略模式可能会显得过于复杂,增加了额外的开发成本。在设计中应权衡使用这种模式的必要性和实际需求。
  3. 维护复杂性: 当系统中的策略逻辑复杂且频繁变动时,维护可能变得困难。每次新增或修改策略时,都需要相应地添加或修改具体的策略类,这可能增加代码的维护复杂性。
  4. 性能影响: 使用反射和动态创建对象可能会带来一定的性能开销。在高性能要求的场景下,需要考虑这种模式可能带来的性能影响。
  5. 不适用于复杂条件: 当条件逻辑非常复杂且难以用简单的注解和策略模式来描述时,这种模式可能不适用。在这种情况下,可能需要采用其他设计模式或更加直接的条件判断来实现复杂逻辑。

所以,虽然自定义注解和策略模式在一些场景下能够带来优雅的代码设计和动态查询功能,但在实际使用中也需要权衡其优缺点。根据具体的应用场景和需求,选择合适的设计模式来实现功能,并保持代码的简洁性和易维护性。

拓展作业

除了策略模式,还有其他设计模式可以用于实现动态查询,如工厂模式、观察者模式等。根据具体需求,选择合适的设计模式来实现动态查询功能。

总结

通过使用自定义注解和策略模式,我们成功地重构了分类查询接口,并实现了动态查询功能。现在,我们可以根据不同的类型动态创建对象,并根据其属性来封装查询结果,代码更加优雅、灵活和可维护。这种优雅的重构方式使得我们的代码更加易于扩展,添加新的兑换类型时无需修改现有的代码,而只需要创建新的查询策略类并标记相应的类型即可。这种设计模式在实际开发中有着广泛的应用,为我们的代码带来了更好的可读性和可维护性。