【DAY 4】
你可以把的服务员 (Controller/App层)端来一个盘子,盘子里放着顾客的点单条(用户ID、菜品ID)。这个盘子就是 RaffleFactorEntity(输入)。好处:服务员不需要左手拿用户ID,右手拿策略ID,直接端个盘子就行。大厨 (Domain层)接过盘子,开始根据单子炒菜(执行抽奖逻辑、规则过滤)。大厨炒好菜后,不会直接把菜扔给服务员,而是装在一个精美的餐盒里,餐盒上还贴着标签(
RaffleFactorEntity (抽奖因子实体)的作用。
1. 它是做什么的?
它是抽奖服务的**“入参包”**。它封装了“我要开始抽奖”这件事所需要的所有上下文信息。
-
代码定义:
public class RaffleFactorEntity { private String userId; // 谁在抽? private Long strategyId; // 抽哪个活动的奖? // 未来可能扩展:private String userIp; // 在哪抽? // 未来可能扩展:private Date raffleTime; // 什么时候抽? }
2. 为什么要用它?(设计思想:结果对象模式)
场景假设: 如果没有这个实体,你的抽奖接口可能只返回一个 Integer awardId。
Java
// ❌ 信息太少
public Integer performRaffle(...)
调用者拿到 101 这个数字是一脸懵逼的。他还需要再去查一遍数据库:“101 是啥?是积分还是iPhone?”这会造成额外的数据库压力。
使用 RaffleAwardEntity 的好处:
-
信息全:抽奖结束的那一刻,领域层直接把“奖品ID”、“奖品名称”、“奖品配置(如发多少积分)”全部打包返回。应用层拿到后,可以直接展示给用户,或者直接发奖,不用再查库。
-
业务闭环:它代表了一个完整的业务结果。
二、 RaffleAwardEntity (抽奖奖品实体)
1. 它是做什么的?
它是抽奖服务的**“出参包”**。它不仅仅告诉你是中奖了还是没中,还包含了奖品的详细描述。
-
代码定义:
Javapublic class RaffleAwardEntity { private Long strategyId; // 策略ID private Integer awardId; // 奖品ID (比如 101) private String awardKey; // 奖品Key (比如 "user_point") private String awardConfig; // 奖品配置 (比如 "100") private String awardDesc; // 描述 (比如 "100积分") }
2. 为什么要用它?(设计思想:结果对象模式)
场景假设: 如果没有这个实体,你的抽奖接口可能只返回一个 Integer awardId。
Java
// ❌ 信息太少
public Integer performRaffle(...)
调用者拿到 101 这个数字是一脸懵逼的。他还需要再去查一遍数据库:“101 是啥?是积分还是iPhone?”这会造成额外的数据库压力。
使用 RaffleAwardEntity 的好处:
-
信息全:抽奖结束的那一刻,领域层直接把“奖品ID”、“奖品名称”、“奖品配置(如发多少积分)”全部打包返回。应用层拿到后,可以直接展示给用户,或者直接发奖,不用再查库。
-
业务闭环:它代表了一个完整的业务结果。
3. 代码中的实际应用
在 AbstractRaffleStrategy 的最后,构建并返回了这个对象:
Java
return RaffleAwardEntity.builder()
.awardId(awardId)
// 未来这里可以把 awardTitle, awardConfig 也填进去
.build();
三、 总结:初学者如何理解?
你可以把 AbstractRaffleStrategy 的 performRaffle 方法看作是一个**“大厨做菜”**的过程:
-
服务员 (Controller/App层) 端来一个盘子,盘子里放着顾客的点单条(用户ID、菜品ID)。
-
这个盘子就是
RaffleFactorEntity(输入)。 -
好处:服务员不需要左手拿用户ID,右手拿策略ID,直接端个盘子就行。
-
-
大厨 (Domain层) 接过盘子,开始根据单子炒菜(执行抽奖逻辑、规则过滤)。
-
大厨 炒好菜后,不会直接把菜扔给服务员,而是装在一个精美的餐盒里,餐盒上还贴着标签(菜名、热量、口味)。
-
这个餐盒就是
RaffleAwardEntity(输出)。 -
好处:服务员拿到餐盒,看一眼标签就知道是什么菜,可以直接端给客户,不用再跑回厨房问大厨“这是啥?”。
-
总结: 这两个实体是为了让数据在不同层级之间传输得更体面、更规范、更易于扩展。这是企业级开发中非常重要的“封装”思想。
详细解释:泛型设计 <T>: 注意它使用了泛型 T extends RuleActionEntity.RaffleEntity。这意味着不同的阶段(抽奖前、中、后)可以返回不同的数据结构,非常灵活。为什么一定是RuleActionEntity.RaffleEntity
1. 为什么要设计一个空的基类 RaffleEntity?
在源码中,你看到了这样一个看似“没用”的类:
Java
// 基类:虽然现在是空的,但它是一个“标记”
static public class RaffleEntity {
}
以及它的子类:
Java
// 抽奖前具体的实体
static public class RaffleBeforeEntity extends RaffleEntity { ... }
// 抽奖中具体的实体
static public class RaffleCenterEntity extends RaffleEntity { ... }
原因分析: 这在设计模式中被称为 Marker Class(标记类) 或 Base Class Constraint(基类约束)。
-
身份证作用:
RaffleEntity就像是一个“通行证”。它告诉编译器和开发者:“只有持有这个通行证的对象,才能被放进RuleActionEntity的data字段里。” -
语义归类: 它把
RaffleBeforeEntity(抽奖前数据)、RaffleAfterEntity(抽奖后数据)强行归为一类。这在业务上定义了边界——这些都是“抽奖流程中的数据”。
2. 泛型约束 <T extends ...> 的核心价值
如果不加 extends RuleActionEntity.RaffleEntity,代码会变成这样:
Java
// 错误的示范:没有任何约束
public class RuleActionEntity<T> {
private T data;
}
如果不加约束,会发生什么灾难?
-
乱塞数据: 开发者可以写出
RuleActionEntity<String>或者RuleActionEntity<Integer>。-
比如:
new RuleActionEntity<String>().setData("垃圾数据")。 -
这在编译时是不会报错的,但在业务逻辑上是完全错误的。抽奖规则的返回结果不应该是一个简单的字符串。
-
-
类型失控: 当你在处理这些数据时,因为不知道
T到底是什么,只能把它当做Object处理,或者进行危险的强制类型转换。
加上约束后 <T extends RuleActionEntity.RaffleEntity> 的好处:
-
编译期拦截: 如果有人试图写
RuleActionEntity<String>,IDE 直接红线报错。编译器强迫你必须传入RaffleEntity的子类。 -
明确契约: 这个泛型声明本身就是文档。它告诉阅读代码的人:“这个
data字段一定包含的是抽奖相关的数据实体,而不是别的。”
3. “灵活”体现在哪里?(不同阶段返回不同结构)
让我们看看业务场景。
场景 A:抽奖前(Before) 在抽奖还没开始时,我们需要决策的是:“要不要拦截?如果要权重抽奖,权重Key是多少?” 所以 RaffleBeforeEntity 长这样:
Java
//
static public class RaffleBeforeEntity extends RaffleEntity {
private Long strategyId; // 策略ID
private String ruleWeightValueKey; // 权重Key(核心字段)
private Integer awardId; // 兜底奖品ID
}
此时,我们定义:RuleActionEntity<RaffleBeforeEntity>。
场景 B:抽奖后(After) 假设抽奖结束了,我们需要记录日志或者发积分。这时候 strategyId 可能已经不重要了,我们需要的是“中奖金额”、“发货单号”。 如果复用同一个对象,那么 RaffleBeforeEntity 里就会塞满一堆空字段。 通过泛型,我们可以定义一个新的子类:
Java
// 假设的代码
static public class RaffleAfterEntity extends RaffleEntity {
private Double amount; // 中奖金额
private String orderId; // 发货单号
}
此时,我们定义:RuleActionEntity<RaffleAfterEntity>。
结论: 同一个 RuleActionEntity 容器类(包含 code, info, ruleModel 等公共字段),通过携带不同的 T(RaffleBeforeEntity 或 RaffleAfterEntity),完美适配了整个抽奖生命周期的不同阶段,而且互不干扰。
4. 总结:为什么一定要是 extends RuleActionEntity.RaffleEntity?
用一句话总结:为了在享受泛型带来的“灵活性”的同时,不丢失“严谨性”。
-
灵活性: 允许
data字段的结构随业务阶段(前、中、后)变化。 -
严谨性(extends 作用): 也就是你问的“为什么一定是它”。它划定了一个圈,禁止非抽奖领域的数据对象混入,保证了代码在复杂业务流转中的纯洁性和安全性。
自定义注解:
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 策略自定义枚举
* @create 2023-12-31 11:29
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogicStrategy {
DefaultLogicFactory.LogicModel logicMode();
}
这段代码定义了一个 Java 自定义注解 LogicStrategy,作用是标记类属于哪种策略模型,常用于策略模式的代码设计中。
注解的元注解:控制注解的作用范围和生命周期:
@Target({ElementType.TYPE})
Target表示:这个注解能标注在什么位置。ElementType.TYPE表示:只能标注在 类、接口、枚举 上,不能标注在方法、变量上。-
@Retention(RetentionPolicy.RUNTIME)Retention表示:这个注解的生命周期(什么时候有效)。RetentionPolicy.RUNTIME表示:注解在 运行时仍然有效,可以通过 Java 反射机制读取这个注解的信息。- 这是这个注解的核心价值:程序运行时,能通过代码判断某个类标注的策略类型,从而执行对应的逻辑。
更多推荐


所有评论(0)