设计目标

根据项目需求,设计一个灵活的物品验证系统,使其能够根据不同的物品类型,动态地选择对应的验证逻辑。不同类型的物品可能需要不同的验证规则,因此系统应能够通过物品类型,自动选择合适的验证器进行验证。本次用到 **策略+工厂 **的设计模式。

策略模式与工厂模式简介

策略模式(Strategy Pattern)

策略模式定义了一系列算法(或策略),并将每个算法封装起来,使得它们可以互相替换。策略模式让算法的变化独立于使用算法的客户端,使得算法的使用更加灵活。

在我们的例子中,策略模式将被用于选择合适的验证逻辑,针对不同的物品类型,选择不同的验证器进行验证。

工厂模式(Factory Pattern)

工厂模式通过定义一个创建对象的接口,让子类决定实例化哪一个类。它将对象的创建过程封装起来,客户端不需要直接使用new操作符来创建对象,从而减少了与具体实现的耦合度。

在我们的例子中,工厂模式用于根据物品类型动态地获取相应的验证器。

二者区别

  • 策略模式的主要目的是行为的动态选择,关键点是策略的封装和调用。
  • 工厂模式的主要目的是创建对象,隐藏对象的具体实现细节,并且提供一个接口用于获取实例对象。

在后续的代码中其实是把二者混合在一起使用了, 在一个类中即完成了实例的注册,也完成了策略的选取,你可以把它分开实现,做个注册器和一个选择器,不过我觉得没太大必要,还要多引入一个类。

代码示例

业务验证工厂类 ItemValidatorFactory

ItemValidatorFactory 类负责根据物品类型(itemType)来选择合适的验证器。它通过工厂方法getValidator来获取一个验证器实例,该方法根据物品类型返回相应的验证器。如果没有找到对应的验证器,则默认使用DefaultItemValidator作为验证器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Component
public class ItemValidatorFactory {

/**
* 保存所有验证器实例
*/
private final Map<ItemTypeEnum, ItemValidator> validatorMap = new HashMap<>();

/**
* 注册验证器实例
*
* @param validators
*/
@Autowired
public ItemValidatorFactory(List<ItemValidator> validators) {
// 将所有 ItemValidator 实现类注册到 Map 中
validators.forEach(validator -> validatorMap.put(validator.getItemType(), validator));
}

/**
* 根据物品类型获取对应的业务验证器
*
* @param itemType
* @return {@link ItemValidator}
*/
public ItemValidator getValidator(Integer itemType) {
ItemTypeEnum itemTypeEnum = ItemTypeEnum.of(itemType);
// 根据物品类型获取校验器,未找到时使用默认校验器
return validatorMap.getOrDefault(itemTypeEnum, new DefaultItemValidator());
}
}

代码中利用Map来存储所有的验证器实例,并通过ItemValidatorFactory的构造函数动态注入所有实现了ItemValidator接口的验证器类。getValidator方法根据物品类型查找对应的验证器,如果找不到则返回一个默认的验证器DefaultItemValidator

验证器接口 ItemValidator

ItemValidator接口定义了所有验证器类的共同方法,所有具体的验证器都需要实现这个接口。不同类型的物品可能有不同的验证需求,因此每种验证器会有自己的具体实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface ItemValidator {

/**
* 校验物品是否合法
*
* @param uid 用户ID
* @param itemId 物品ID
*/
boolean validate(Long uid, Long itemId);

/**
* 获取当前验证器对应的物品类型
*
* @return {@link ItemTypeEnum}
*/
ItemTypeEnum getItemType();
}

具体的验证器 BadgeValidator

我们创建了一个名为BadgeValidator的验证器,它负责验证某种类型的物品——徽章是否可以发放。具体的验证逻辑在validate方法中实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 徽章发放业务验证器
* @author Ershi
* @date 2024/12/08
*/
@Component
@Data
public class BadgeValidator implements ItemValidator {

/**
* 当前验证其对应的物品类型
*/
private static final ItemTypeEnum ITEM_TYPE = ItemTypeEnum.BADGE;

@Autowired
private UserBackpackDao userBackpackDao;

/**
* 验证期望发放的徽章是否已经存在
* @param uid 用户id
* @param itemId 徽章id
* @return boolean 若徽章已存在则返回false,反之返回true
*/
@Override
public boolean validate(Long uid, Long itemId) {
Integer countByValidItemId = userBackpackDao.getCountByValidItemId(uid, itemId);
return countByValidItemId <= 0;
}

/**
* 获取当前验证器对应的物品类型
*
* @return {@link ItemTypeEnum}
*/
@Override
public ItemTypeEnum getItemType() {
return ITEM_TYPE;
}
}

同时需要通过一个属性ITEM_TYPE来标识当前验证器属于哪类物品,以供工厂选择。

使用策略模式进行验证

在业务逻辑中,我们通过ItemValidatorFactory获取相应的验证器,并调用验证器的validate方法进行验证。

1
2
3
4
5
6
7
8
ItemConfig itemConfig = itemCache.getById(itemId);
Integer itemType = itemConfig.getType();
ItemValidator validator = itemValidatorFactory.getValidator(itemType);

if (!validator.validate(uid, itemId)) {
// 未通过检查 => 不继续发放
return;
}

在这段代码中,首先根据物品ID获取物品配置,进而获取物品的类型。然后,通过ItemValidatorFactorygetValidator方法根据物品类型动态选择对应的验证器,并调用validate方法来检查是否可以发放该物品。

扩展性

通过结合策略模式和工厂模式,我们的验证系统具有良好的扩展性。当新的物品类型需要新增验证规则时,只需要做以下几步:

  1. 创建新的验证器类、继承 ItemValidator 接口、通过ITEM_TYPE标识当前类属于哪类物品、编写对应的验证逻辑。
  2. ItemValidatorFactory 中,确保所有新的验证器都能通过 validators 列表自动注入。
  3. 无需修改现有的业务代码,只需确保新的物品类型能够正确对应到新的验证器。

例如,若要新增一种新的物品类型(如“组队卡”),只需要创建一个 ItemValidator 实现类(如TeamCardValidator),并写入 ITEM_TYPE,其他现有的业务逻辑都无需做任何修改。

应用

策略模式和工厂模式结合使用的场景主要是在需要动态选择策略策略的创建过程较复杂时,工厂模式负责策略的创建,策略模式负责策略的行为封装。

比如:

  1. 支付系统中的多种支付方式
  2. 订单优惠计算:电商系统中的优惠计算,如满减、折扣、积分兑换等,不同的优惠计算逻辑可以使用策略模式管理,而工厂模式根据配置(如数据库或文件)动态加载相应的策略。
  3. 日志记录系统:
  4. **文件格式解析: **

总结

通过结合策略模式和工厂模式,我们实现了一个可扩展的动态业务验证系统。策略模式让我们能够根据物品类型动态选择验证逻辑,而工厂模式则确保了验证器的实例化过程与客户端解耦。通过这种设计,我们能够快速响应业务需求的变化,轻松添加新的验证逻辑,保持代码的清晰和可维护性。