一、让我们先来看一段非常蠢的策略切换
// 将所有的策略类以list的形式注入
@Resource
private List<PoiExtExtraInfoToBService> poiExtExtraInfoToBServices;
// 通过反射获取策略类的code值然后比对入参code得到所需策略类
List<PoiExtExtraInfoToBService> satisfyServices = poiExtExtraInfoToBServices.stream().filter(poiExtExtraInfoToBService ->
reqDTO.getPoiExtCategoryId().equals(poiExtExtraInfoToBService.getClass().getAnnotation(PoiExtBizCategory.class).bizCategoryId())).collect(Collectors.toList());
这段Java代码主要实现了以下功能:
-
依赖注入策略类列表:使用了
@Resource
注解来自动注入实现了PoiExtExtraInfoToBService
接口的所有类的实例列表。这些策略类的实例被存储在名为poiExtExtraInfoToBServices
的List
集合中。 -
筛选特定策略实现:通过使用流(Stream)操作,从
poiExtExtraInfoToBServices
列表中筛选出符合特定条件的策略实现。这个条件是策略实现类上的@PoiExtBizCategory
注解中的bizCategoryId
属性值与请求DTO (reqDTO
) 中的poiExtCategoryId
属性值相匹配。 -
使用反射获取注解值:在筛选过程中,代码使用了反射来访问每个策略实现类上的
@PoiExtBizCategory
注解,特别是该注解的bizCategoryId
属性。反射是必要的,因为这些值在运行时被用来动态确定哪些策略类与当前请求的类别ID匹配。 -
收集匹配的策略实现:匹配的策略实现被收集到一个新的列表
satisfyServices
中,这个列表包含所有其类别ID符合条件的策略实现实例。
这段代码的详细流程如下:
- 流程开始:从
poiExtExtraInfoToBServices
列表中开始一个流式处理。 - 筛选(Filter):对于列表中的每一个元素(每个策略实现),执行以下操作:
- 获取该策略实现类的
Class
对象。 - 通过
getClass().getAnnotation(PoiExtBizCategory.class)
获取该类上的PoiExtBizCategory
注解。 - 从注解实例中获取
bizCategoryId
值。 - 将此值与
reqDTO
中的poiExtCategoryId
进行比较。 - 如果这两个值相等,说明这个策略实现是符合条件的。
- 获取该策略实现类的
- 收集结果:使用
collect(Collectors.toList())
将所有符合条件的策略实现收集到一个新的列表satisfyServices
。
缺点:
-
性能影响:反射是一个相对昂贵的操作,特别是在大量调用时。访问注解和属性通过反射进行,会比直接的方法调用慢。在性能敏感的应用中,频繁使用反射可能成为瓶颈。
-
代码复杂性:使用反射和流式API虽然在功能上非常强大,但这也可能使代码更难理解和维护。特别是反射部分,错误处理和调试可能会更加困难,因为反射操作的错误可能不会在编译时被捕获,而是在运行时显现。
-
安全性问题:如之前所述,反射可以绕过Java的访问控制机制,访问私有和受保护的成员。这可能导致意外的行为或安全漏洞,特别是在不正确地管理反射访问权限的情况下。
-
封装性破坏:反射的使用可能会破坏对象的封装性,因为它允许外部代码访问和修改类的内部状态。这与面向对象编程的基本原则相违背,可能会导致代码难以维护和扩展。
-
代码的可靠性:由于反射依赖于运行时的动态类型信息,任何重构活动(如重命名字段或移除方法)都可能不被反射代码正确识别,从而导致运行时错误。
-
类型安全问题:反射操作通常不是类型安全的。这意味着如果注解或其属性的类型在未来更改,相关的反射代码可能会失败,且这种失败只能在运行时被检测到。
-
注入了不必要的策略类:在定制化逻辑中注入了不必要的策略类,使业务变得难以理解。
二、通过枚举类实现策略的上下文切换
/**
* 枚举类,定义了业务分类的枚举值
*/
public enum BizCategoryEnum {
HOME_DECORATION_DESIGN_SPECIAL_SERVICE(60103, "家居装修设计特色服务", new HomeDecorationDesignSpecialServiceStrategyImpl()),
DISCOVERY_VIDEO(28904,"探店视频", new DiscoveryVideoStrategyImpl());
private final Integer code;
private final String desc;
private final BizCategoryStrategy strategy;
BizCategoryEnum(Integer code, String desc, BizCategoryStrategy strategy) {
this.code = code;
this.desc = desc;
this.strategy = strategy;
}
public Integer getCode() {
return code;
}
public String getDesc() {
return desc;
}
public void executeStrategy(ProcessorContext context) {
strategy.invoke(context);
}
public static BizCategoryEnum fromCode(Integer code) {
for (BizCategoryEnum value : values()) {
if (value.getCode().equals(code)) {
return value;
}
}
return null;
}
}
// 执行策略
BizCategoryEnum category = BizCategoryEnum.fromCode(context.getPoiExtCategoryId());
if (category != null) { // 确保枚举值不是null
category.executeStrategy(context);
}
这段代码演示了如何在Java中使用枚举来实现策略模式,优雅地结合了业务逻辑的封装和策略执行。下面是对这个实现的详细分析:
枚举类 BizCategoryEnum
说明
-
定义枚举值:
- 每个枚举值(如
HOME_DECORATION_DESIGN_SPECIAL_SERVICE
和DISCOVERY_VIDEO
)都关联了一个唯一的业务代码(code)、描述(desc)以及具体的策略实现(strategy)。这些策略实现都实现了某个BizCategoryStrategy
接口。
- 每个枚举值(如
-
字段:
code
:业务分类的唯一代码。desc
:对业务分类的描述。strategy
:具体的策略实现,用于执行与业务分类相关的特定逻辑。
-
构造函数:
- 构造函数接收业务代码、描述和策略实现,为每个枚举值设置这些属性。
-
方法:
getCode()
和getDesc()
:提供对枚举值属性的访问。executeStrategy(ProcessorContext context)
:调用策略实现的invoke
方法,传入上下文,执行策略。fromCode(Integer code)
:静态方法,根据传入的业务代码查找对应的枚举值。这是实现策略选择的关键方法。
使用示例
- 策略执行逻辑:
- 使用
fromCode
方法根据上下文中的业务代码查找相应的枚举值。 - 如果找到相应的枚举值,调用
executeStrategy
方法执行关联的策略。
- 使用
优点
- 封装性:将业务代码、描述和策略实现封装在枚举值中,易于管理和使用。
- 类型安全:使用枚举保证了类型安全,避免了使用字符串或整数常量的潜在错误。
- 易于扩展:新增业务分类和策略时,只需添加新的枚举值即可。
- 清晰的业务逻辑分离:通过分离策略接口和具体实现,保持了代码的模块化和高内聚。