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 的好处

  1. 信息全:抽奖结束的那一刻,领域层直接把“奖品ID”、“奖品名称”、“奖品配置(如发多少积分)”全部打包返回。应用层拿到后,可以直接展示给用户,或者直接发奖,不用再查库。

  2. 业务闭环:它代表了一个完整的业务结果

二、 RaffleAwardEntity (抽奖奖品实体)

1. 它是做什么的?

它是抽奖服务的**“出参包”**。它不仅仅告诉你是中奖了还是没中,还包含了奖品的详细描述。

  • 代码定义:

    Java

    public 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 的好处

  1. 信息全:抽奖结束的那一刻,领域层直接把“奖品ID”、“奖品名称”、“奖品配置(如发多少积分)”全部打包返回。应用层拿到后,可以直接展示给用户,或者直接发奖,不用再查库。

  2. 业务闭环:它代表了一个完整的业务结果

3. 代码中的实际应用

AbstractRaffleStrategy 的最后,构建并返回了这个对象:

Java

return RaffleAwardEntity.builder()
        .awardId(awardId)
        // 未来这里可以把 awardTitle, awardConfig 也填进去
        .build();

三、 总结:初学者如何理解?

你可以把 AbstractRaffleStrategyperformRaffle 方法看作是一个**“大厨做菜”**的过程:

  1. 服务员 (Controller/App层) 端来一个盘子,盘子里放着顾客的点单条(用户ID、菜品ID)。

    • 这个盘子就是 RaffleFactorEntity (输入)

    • 好处:服务员不需要左手拿用户ID,右手拿策略ID,直接端个盘子就行。

  2. 大厨 (Domain层) 接过盘子,开始根据单子炒菜(执行抽奖逻辑、规则过滤)。

  3. 大厨 炒好菜后,不会直接把菜扔给服务员,而是装在一个精美的餐盒里,餐盒上还贴着标签(菜名、热量、口味)。

    • 这个餐盒就是 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 就像是一个“通行证”。它告诉编译器和开发者:“只有持有这个通行证的对象,才能被放进 RuleActionEntitydata 字段里。”

  • 语义归类: 它把 RaffleBeforeEntity(抽奖前数据)、RaffleAfterEntity(抽奖后数据)强行归为一类。这在业务上定义了边界——这些都是“抽奖流程中的数据”。


2. 泛型约束 <T extends ...> 的核心价值

如果不加 extends RuleActionEntity.RaffleEntity,代码会变成这样:

Java

// 错误的示范:没有任何约束
public class RuleActionEntity<T> {
    private T data;
}

如果不加约束,会发生什么灾难?

  1. 乱塞数据: 开发者可以写出 RuleActionEntity<String> 或者 RuleActionEntity<Integer>

    • 比如:new RuleActionEntity<String>().setData("垃圾数据")

    • 这在编译时是不会报错的,但在业务逻辑上是完全错误的。抽奖规则的返回结果不应该是一个简单的字符串。

  2. 类型失控: 当你在处理这些数据时,因为不知道 T 到底是什么,只能把它当做 Object 处理,或者进行危险的强制类型转换。

加上约束后 <T extends RuleActionEntity.RaffleEntity> 的好处:

  1. 编译期拦截: 如果有人试图写 RuleActionEntity<String>,IDE 直接红线报错。编译器强迫你必须传入 RaffleEntity 的子类。

  2. 明确契约: 这个泛型声明本身就是文档。它告诉阅读代码的人:“这个 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 反射机制读取这个注解的信息。
    • 这是这个注解的核心价值:程序运行时,能通过代码判断某个类标注的策略类型,从而执行对应的逻辑。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐