零基础鸿蒙应用开发第二十九节:策略模式重构电商促销系统

零基础鸿蒙应用开发学习计划表

【学习目标】

  1. 掌握策略模式核心思想,实现电商促销场景下“商品属性”与“促销计算”的职责分离;
  2. 解决传统促销系统类臃肿、拓展难、叠加逻辑混乱等核心痛点;
  3. 理解PromotionManager非单例设计逻辑,落地“规则池+精准ID管控”的商户运营需求;
  4. 掌握泛型策略接口与枚举的组合用法,保障代码编译期类型安全;
  5. 实现“同类型选最优、不同类型叠加、按ID精准取消”的促销规则逻辑;
  6. 理解ArkTS工程化设计原则,区分单例/非单例管理器的适用场景。

【学习重点】

  1. 核心解耦:将促销计算逻辑从商品类抽离为独立策略类,商品类仅负责属性管理;
  2. 规则管控:外部传入规则ID、按ID置规则无效(非删除)、同类型规则自动选最优;
  3. 管理器设计PromotionManager非单例实现商品规则隔离,GoodsManager单例保障全局商品数据一致;
  4. 类型安全:通过IPromotionStrategy<T>泛型接口+PromotionType枚举,避免硬编码与类型错误;
  5. 场景适配:落地多档位满减校验、规则叠加顺序配置、价格非负兜底等商户实际需求。

一、工程结构

复制工程ClassObjectDemo_7,重命名为ClassObjectDemo_8,核心文件调整如下(仅展示变更/新增文件):

ClassObjectDemo_8/
├── entry/src/main/ets/
│   ├── pages/
│   │   └── Index.ets           # 演示入口:商品初始化、规则展示、交互操作
│   ├── model/
│   │   ├── entity/             # 商品实体层(仅管理属性)
│   │   │   ├── AbstractGoods.ets  # 抽象商品基类:聚合促销管理器,提供规则操作入口
│   │   │   ├── BookGoods.ets    # 图书商品:扩展作者、出版日期属性
│   │   │   ├── DigitalGoods.ets # 数码商品:扩展品牌、保修期属性
│   │   │   └── GoodsStatus.ets  # 商品状态枚举(复用)
│   │   ├── interface/          # 契约层
│   │   │   ├── attribute/      # 商品属性接口(复用)
│   │   │   ├── behavior/       # 商品行为接口(复用)
│   │   │   └── promotion/      # 促销规则接口(重构多档位满减)
│   │   ├── strategy/           # 促销策略实现层(核心新增)
│   │   │   ├── IPromotionStrategy.ets # 泛型策略接口:统一策略规范
│   │   │   ├── PromotionType.ets      # 促销类型枚举:折扣/满减
│   │   │   ├── DiscountStrategy.ets   # 折扣策略:最优规则筛选与价格计算
│   │   │   └── FullReductionStrategy.ets # 满减策略:多档位匹配与最优减免
│   │   └── utils/              # 工具层
│   │       ├── IdGenerator.ets        # 唯一ID生成:商品/规则ID
│   │       ├── PromotionValidator.ets # 规则校验:通用+专属校验分离
│   │       └── PromotionManager.ets   # 策略管理器:非单例,管控策略注册与叠加计算
└── oh-package.json5                # 项目依赖(复用)

二、核心问题分析

传统促销系统中,商品类需直接实现IDiscountable/IFullReductionable等接口,存在以下致命痛点:

痛点 具体影响
类臃肿 商品类同时维护属性(作者/品牌)与促销计算逻辑,代码量庞大,职责混乱
拓展困难 新增促销类型(如优惠券)需修改所有商品类,违反开闭原则,易引发连锁bug
叠加逻辑僵化 多规则执行顺序固定在商品类中,无法灵活适配商户“先满减后折扣”等需求
复用性差 不同商品的同类促销规则无法复用,重复代码大量滋生
管控粒度粗糙 仅支持整类促销取消,无法精准暂停单条活动,易引发运营误操作

三、核心设计思路

基于策略模式重构,核心思路为职责分离、契约约束、统一管控

  1. 职责分离:将促销计算逻辑抽离为独立策略类,商品类仅管理自身属性,促销管理器负责策略调度;
  2. 策略类设计:折扣/满减策略类各自实现促销逻辑,通过泛型接口约束行为一致性;
  3. 商品抽象类设计:每个商品实例独立持有促销管理器,实现规则隔离;封装属性校验、价格/利润计算等通用能力,子类仅需扩展特有属性;
  4. 商户规则落地:支持同类型规则选最优、多档位满减匹配、按ID精准取消(置无效而非删除)、规则ID外部传入保证可追溯;
  5. 类型安全约束:通过泛型策略接口限定规则类型,枚举统一管理促销类型,避免硬编码错误。
  6. 可拓展性:新增促销类型(如优惠券、拼团折扣)仅需实现IPromotionStrategy泛型接口,无需修改原有策略类和商品类;促销管理器支持动态注册新策略,核心调度逻辑无需改动;规则结构可通过接口继承扩展(如新增 “拼团人数”“优惠券门槛” 等字段),兼容个性化促销规则需求。

四、核心工具与契约定义

4.1 自定义ID工具

// model/utils/IdGenerator.ets
/**
 * 唯一ID生成工具
 * ID结构:前缀 + 16进制时间戳 + 4位补零随机数
 */
export class IdGenerator {
  public static generatePromotionId(prefix: string): string {
    const timestamp = Date.now().toString(16);
    const random = Math.floor(Math.random() * 10000).toString().padStart(4, '0');
    return `${prefix}_${timestamp}_${random}`;
  }
}

4.2 促销类型枚举

// model/strategy/PromotionType.ets
// 促销类型枚举:统一管理类型,避免硬编码
export enum PromotionType {
  /** 折扣策略 */
  DISCOUNT = "DISCOUNT",
  /** 满减策略 */
  FULL_REDUCTION = "FULL_REDUCTION"
}

4.3 多档位满减规则(重构)

// model/interface/promotion/IFullReductionRule.ets
import { IPromotionRule } from './IPromotionRule';

/** 满减档位:单档位满减规则(满X减Y) */
export interface IReductionTier {
  fullAmount: number;     // 满减门槛(元)
  reductionAmount: number;// 减免金额(元)
}

/** 满减规则:支持多档位配置 */
export interface IFullReductionRule extends IPromotionRule {
  tiers: IReductionTier[]; // 多档位满减规则
}

4.4 泛型促销策略接口

// model/strategy/IPromotionStrategy.ets
import { PromotionType } from './PromotionType';
import { IPromotionRule } from '../interface/promotion/IPromotionRule';

/**
 * 泛型促销策略接口:所有促销策略的统一契约
 * @template T 约束为IPromotionRule子类,保证类型安全
 */
export interface IPromotionStrategy<T extends IPromotionRule> {
  /** 促销策略类型标识 */
  type: PromotionType;

  /** 添加促销规则到规则池 */
  addRule(rule: T): boolean;

  /** 按规则ID取消规则(置为无效) */
  cancelRuleById(promotionId: string): boolean;

  /** 获取当前策略下的最优促销规则 */
  getBestRule(actualPrice?: number): T | undefined;

  /** 计算应用最优规则后的商品价格 */
  calculate(originalPrice: number): number;

  /** 判断当前策略是否有生效的最优规则 */
  isEffective(actualPrice?: number): boolean;

  /** 按ID查询促销规则 */
  getRuleById(promotionId: string): T | undefined;

  /** 获取策略中所有促销规则(返回副本) */
  getRules(): T[];
}

4.5 促销规则校验工具

// model/utils/PromotionValidator.ets
import { IPromotionRule } from '../interface/promotion/IPromotionRule';
import { IDiscountRule } from '../interface/promotion/IDiscountRule';
import { IFullReductionRule, IReductionTier } from '../interface/promotion/IFullReductionRule';

/** 促销规则校验工具:通用+专属校验分离 */
export class PromotionValidator {
  /** 基础规则校验(时间、启用状态) */
  private static validateBaseRule(rule: IPromotionRule): boolean {
    const now = new Date();
    if (rule.startTime > now || rule.endTime < now) return false;
    return rule.isEnabled;
  }

  /** 折扣规则校验(折扣率0-1) */
  public static validateDiscountRule(rule: IDiscountRule): boolean {
    if (!PromotionValidator.validateBaseRule(rule)) return false;
    return rule.discountRate > 0 && rule.discountRate <= 1;
  }

  /** 满减规则校验(多档位金额合法) */
  public static validateFullReductionRule(rule: IFullReductionRule): boolean {
    if (!PromotionValidator.validateBaseRule(rule)) return false;
    if (!rule.tiers || rule.tiers.length === 0) return false;

    const isValidTier = (tier: IReductionTier): boolean => {
      return tier.fullAmount > 0 && tier.reductionAmount > 0 && tier.reductionAmount < tier.fullAmount;
    };
    return rule.tiers.every(isValidTier);
  }
}

五、促销策略实现

5.1 折扣策略类

// model/strategy/DiscountStrategy.ets
import { IPromotionStrategy } from './IPromotionStrategy';
import { PromotionType } from './PromotionType';
import { IDiscountRule } from '../interface/promotion/IDiscountRule';
import { PromotionValidator } from '../utils/PromotionValidator';

/** 折扣策略:最优规则筛选+价格计算 */
export class DiscountStrategy implements IPromotionStrategy<IDiscountRule> {
  type: PromotionType = PromotionType.DISCOUNT;
  private rulePool: IDiscountRule[] = [];
  
  // 添加折扣规则(校验合法性+ID唯一性)
  addRule(rule: IDiscountRule): boolean {
    if (!PromotionValidator.validateDiscountRule(rule)) {
      console.warn(`【折扣策略】规则无效,跳过添加:${rule.promotionName || '未知规则'}`);
      return false;
    }
    if (this.rulePool.some(r => r.promotionId === rule.promotionId)) {
      console.warn(`【折扣策略】规则ID已存在,跳过添加:${rule.promotionId}`);
      return false;
    }
    this.rulePool.push(rule);
    console.log(`【折扣策略】新增规则:${rule.promotionName}(ID:${rule.promotionId},折扣率${(rule.discountRate * 10).toFixed(1)}折)`);
    return true;
  }

  // 按ID取消单条折扣规则(置无效)
  cancelRuleById(promotionId: string): boolean {
    const targetRule = this.rulePool.find(rule => rule.promotionId === promotionId);
    if (!targetRule) {
      console.warn(`【折扣策略】未找到ID为${promotionId}的规则,取消失败`);
      return false;
    }
    targetRule.isEnabled = false;
    console.log(`【折扣策略】已取消ID为${promotionId}的规则:${targetRule.promotionName || '未知规则'}`);
    return true;
  }

  // 按ID查询折扣规则
  getRuleById(promotionId: string): IDiscountRule | undefined {
    return this.rulePool.find(rule => rule.promotionId === promotionId);
  }

  // 获取最优折扣规则(生效规则中折扣率最低)
  getBestRule(_?: number): IDiscountRule | undefined {
    if (this.rulePool.length === 0) return undefined;
    const now = new Date();
    const effectiveRules = this.rulePool.filter(rule =>
      rule.isEnabled &&
      (!rule.startTime || new Date(rule.startTime) <= now) &&
      (!rule.endTime || new Date(rule.endTime) >= now) &&
      rule.discountRate > 0 && rule.discountRate <= 1
    );
    if (effectiveRules.length === 0) return undefined;
    return effectiveRules.reduce((best, current) => {
      return current.discountRate < best.discountRate ? current : best;
    });
  }

  // 计算折扣后价格(非负+保留两位小数)
  calculate(originalPrice: number): number {
    const bestRule = this.getBestRule();
    if (!bestRule) return originalPrice;
    let finalPrice = originalPrice * bestRule.discountRate;
    finalPrice = parseFloat(finalPrice.toFixed(2));
    return Math.max(finalPrice, 0);
  }

  // 判断最优折扣规则是否生效
  isEffective(actualPrice?: number): boolean {
    const bestRule = this.getBestRule(actualPrice);
    return !!bestRule && bestRule.discountRate < 1;
  }

  // 获取所有折扣规则(返回副本)
  getRules(): IDiscountRule[] {
    return [...this.rulePool];
  }
}

5.2 多档位满减策略类

// model/strategy/FullReductionStrategy.ets
import { IPromotionStrategy } from './IPromotionStrategy';
import { PromotionType } from './PromotionType';
import { IFullReductionRule, IReductionTier } from '../interface/promotion/IFullReductionRule';
import { PromotionValidator } from '../utils/PromotionValidator';

/** 满减策略:多档位匹配+最优减免计算 */
export class FullReductionStrategy implements IPromotionStrategy<IFullReductionRule> {
  type: PromotionType = PromotionType.FULL_REDUCTION;
  private rulePool: IFullReductionRule[] = [];

  // 获取所有满减规则(返回副本)
  getRules(): IFullReductionRule[] {
    return [...this.rulePool];
  }

  // 添加满减规则(校验合法性+ID唯一性,档位降序排序)
  addRule(rule: IFullReductionRule): boolean {
    if (!PromotionValidator.validateFullReductionRule(rule)) {
      console.warn(`【满减策略】规则无效,跳过添加:${rule.promotionName || '未知规则'}`);
      return false;
    }
    if (this.rulePool.some(r => r.promotionId === rule.promotionId)) {
      console.warn(`【满减策略】规则ID已存在,跳过添加:${rule.promotionId}`);
      return false;
    }
    rule.tiers = rule.tiers.sort((a: IReductionTier, b: IReductionTier) => b.fullAmount - a.fullAmount);
    this.rulePool.push(rule);
    console.log(`【满减策略】新增规则:${rule.promotionName}(ID:${rule.promotionId},档位:${this.formatTiers(rule.tiers)})`);
    return true;
  }

  // 按ID取消单条满减规则(置无效)
  cancelRuleById(promotionId: string): boolean {
    const targetRule = this.rulePool.find(rule => rule.promotionId === promotionId);
    if (!targetRule) {
      console.warn(`【满减策略】未找到ID为${promotionId}的规则,取消失败`);
      return false;
    }
    targetRule.isEnabled = false;
    console.log(`【满减策略】已取消ID为${promotionId}的规则:${targetRule.promotionName || '未知规则'}`);
    return true;
  }

  // 按ID查询满减规则
  getRuleById(promotionId: string): IFullReductionRule | undefined {
    return this.rulePool.find(rule => rule.promotionId === promotionId);
  }

  // 获取最优满减规则(生效规则中减免金额最多)
  getBestRule(actualPrice?: number): IFullReductionRule | undefined {
    if (this.rulePool.length === 0) return undefined;
    const now = new Date();
    const effectiveRules = this.rulePool.filter(rule =>
      rule.isEnabled &&
      (!rule.startTime || new Date(rule.startTime) <= now) &&
      (!rule.endTime || new Date(rule.endTime) >= now)
    );
    if (effectiveRules.length === 0) return undefined;
    if (!actualPrice) return effectiveRules[0];
    if (isNaN(actualPrice) || actualPrice < 0) {
      console.warn(`【满减策略】实际价格异常(${actualPrice}),默认返回第一个生效规则`);
      return effectiveRules[0];
    }
    return effectiveRules.reduce((best, current) => {
      const bestReduction = this.calculateRuleReduction(best, actualPrice!);
      const currentReduction = this.calculateRuleReduction(current, actualPrice!);
      return currentReduction > bestReduction ? current : best;
    });
  }

  // 计算单个满减规则的最优减免金额
  private calculateRuleReduction(rule: IFullReductionRule, price: number): number {
    if (isNaN(price) || price <= 0) return 0;
    const matchedTier = rule.tiers.find(tier => price >= tier.fullAmount);
    return matchedTier ? matchedTier.reductionAmount : 0;
  }

  // 计算满减后价格(非负+保留两位小数)
  calculate(originalPrice: number): number {
    if (isNaN(originalPrice) || originalPrice < 0) {
      console.warn(`【满减策略】原价异常(${originalPrice}),返回0元`);
      return 0;
    }
    const bestRule = this.getBestRule(originalPrice);
    if (!bestRule) return originalPrice;
    let finalPrice = originalPrice;
    const reductionAmount = this.calculateRuleReduction(bestRule, finalPrice);
    finalPrice -= reductionAmount;
    finalPrice = Math.max(parseFloat(finalPrice.toFixed(2)), 0);
    console.log(`【满减策略】匹配规则:${bestRule.promotionName},减免${reductionAmount.toFixed(2)}元,价格从${originalPrice.toFixed(2)}→${finalPrice.toFixed(2)}`);
    return finalPrice;
  }

  // 判断最优满减规则是否生效
  isEffective(actualPrice?: number): boolean {
    const bestRule = this.getBestRule(actualPrice);
    if (!bestRule || !actualPrice || isNaN(actualPrice) || actualPrice <= 0) return false;
    return this.calculateRuleReduction(bestRule, actualPrice) > 0;
  }

  // 格式化满减档位展示文本
  private formatTiers(tiers: IReductionTier[]): string {
    if (!tiers || !Array.isArray(tiers) || tiers.length === 0) return "无档位";
    return tiers.map(t => `满${t.fullAmount.toFixed(2)}减${t.reductionAmount.toFixed(2)}`).join('、');
  }
}

5.3 促销策略管理器(非单例)

// model/utils/PromotionManager.ets
import { IPromotionStrategy } from '../strategy/IPromotionStrategy';
import { PromotionType } from '../strategy/PromotionType';
import { DiscountStrategy } from '../strategy/DiscountStrategy';
import { FullReductionStrategy } from '../strategy/FullReductionStrategy';
import { IPromotionRule } from '../interface/promotion/IPromotionRule';

/**
 * 促销策略管理器(非单例)
 * 核心:商品规则隔离、动态注册策略、自定义叠加顺序、精准管控规则
 */
export class PromotionManager {
  /** 促销策略数组:记录注册顺序 */
  private strategies: IPromotionStrategy<IPromotionRule>[] = [];
  /** 促销策略执行顺序:默认先折扣后满减 */
  private executeOrder: PromotionType[] = [PromotionType.DISCOUNT, PromotionType.FULL_REDUCTION];
  /** 促销策略缓存Map:提升查找效率 */
  private strategyMap: Map<PromotionType, IPromotionStrategy<IPromotionRule>> = new Map();
  /** 规则ID集合:快速校验ID是否存在 */
  private promotionRuleIds: Record<PromotionType, string[]> = {
    [PromotionType.DISCOUNT]: [],
    [PromotionType.FULL_REDUCTION]: []
  };

  /** 构造函数:非单例,每次创建新实例 */
  constructor() {}

  /** 注册促销策略(避免重复注册) */
  public registerStrategy(strategy: IPromotionStrategy<IPromotionRule>): void {
    if (this.strategyMap.has(strategy.type)) {
      console.warn(`【促销管理器】${strategy.type}策略已存在,无需重复注册`);
      return;
    }
    this.strategies.push(strategy);
    this.strategyMap.set(strategy.type, strategy);
    console.log(`【促销管理器】成功注册${strategy.type}策略`);
  }

  /** 添加促销规则(自动注册策略) */
  public addPromotionRule<T extends IPromotionRule>(type: PromotionType, rule: T): boolean {
    if (!rule || !rule.promotionId) {
      console.warn(`【促销管理器】${type}策略规则/ID为空,添加失败`);
      return false;
    }

    let strategy = this.strategyMap.get(type);
    if (!strategy) {
      strategy = this.createStrategy(type);
      if (!strategy) {
        console.warn(`【促销管理器】创建${type}策略失败`);
        return false;
      }
      this.registerStrategy(strategy);
    }

    const addSuccess = strategy.addRule(rule);
    if (addSuccess && !this.promotionRuleIds[type].includes(rule.promotionId)) {
      this.promotionRuleIds[type].push(rule.promotionId);
      console.log(`【促销管理器】${type}策略规则ID已同步:${rule.promotionId}`);
    }

    return addSuccess;
  }

  /** 创建指定类型的促销策略实例 */
  private createStrategy(type: PromotionType): IPromotionStrategy<IPromotionRule> | undefined {
    switch (type) {
      case PromotionType.DISCOUNT:
        return new DiscountStrategy();
      case PromotionType.FULL_REDUCTION:
        return new FullReductionStrategy();
      default:
        console.warn(`【促销管理器】不支持的促销类型:${type}`);
        return undefined;
    }
  }

  /** 按ID取消促销规则(同步移除ID) */
  public cancelPromotionRule(type: PromotionType, promotionId: string): boolean {
    const strategy = this.strategyMap.get(type);
    if (!strategy) {
      console.warn(`【促销管理器】未找到${type}策略,取消失败`);
      return false;
    }

    const cancelSuccess = strategy.cancelRuleById(promotionId);
    if (cancelSuccess) {
      this.promotionRuleIds[type] = this.promotionRuleIds[type].filter(id => id !== promotionId);
      console.log(`【促销管理器】${type}策略规则ID已移除:${promotionId}`);
    }

    return cancelSuccess;
  }

  /** 按类型+ID获取促销规则 */
  public getPromotionRuleById<T extends IPromotionRule>(type: PromotionType, promotionId: string): T | undefined {
    const strategy = this.strategyMap.get(type) as IPromotionStrategy<T>;
    if (!strategy) {
      console.warn(`【促销管理器】未找到${type}策略`);
      return undefined;
    }
    return strategy.getRuleById(promotionId);
  }

  /** 全局ID查找促销规则 */
  public getPromotionRuleByGlobalId<T extends IPromotionRule>(promotionId: string): T | undefined {
    for (const entry of this.strategyMap.entries()) {
      const typedStrategy = entry[1] as IPromotionStrategy<T>;
      const rule = typedStrategy.getRuleById(promotionId);
      if (rule) {
        console.log(`【促销管理器】全局查找规则成功:ID=${promotionId},类型=${entry[0]}`);
        return rule;
      }
    }
    console.warn(`【促销管理器】未找到ID为${promotionId}的规则`);
    return undefined;
  }

  /** 获取指定类型的所有促销规则(返回副本) */
  public getPromotionRules<T extends IPromotionRule>(type: PromotionType): T[] {
    const strategy = this.strategyMap.get(type) as IPromotionStrategy<T>;
    if (!strategy) {
      console.warn(`【促销管理器】未找到${type}策略`);
      return [];
    }
    return [...strategy.getRules()];
  }

  /** 获取指定类型的最优促销规则 */
  public getBestRule<T extends IPromotionRule>(type: PromotionType, actualPrice?: number): T | undefined {
    const strategy = this.strategyMap.get(type) as IPromotionStrategy<T>;
    if (!strategy) {
      console.warn(`【促销管理器】未找到${type}策略`);
      return undefined;
    }
    return strategy.getBestRule(actualPrice);
  }

  /** 叠加计算商品最终售价 */
  public calculateFinalPrice(originalPrice: number): number {
    if (isNaN(originalPrice) || originalPrice < 0) {
      console.warn(`【促销管理器】原价异常(${originalPrice}元),返回0元`);
      return 0;
    }

    const registeredTypes = this.getRegisteredTypes();
    const validExecuteOrder = this.executeOrder.filter(type => registeredTypes.includes(type));

    if (validExecuteOrder.length === 0) {
      console.log(`【促销管理器】无已注册策略,返回原价:${originalPrice.toFixed(2)}元`);
      return originalPrice;
    }

    let finalPrice = originalPrice;
    console.log(`【促销管理器】开始计算,原价:${originalPrice.toFixed(2)}元,待执行策略:${validExecuteOrder.join('→')}`);

    validExecuteOrder.forEach(type => {
      const strategy = this.strategyMap.get(type);
      if (!strategy) return;

      const allRules = strategy.getRules();
      const effectiveRules = allRules.filter(rule => rule.isEnabled);
      if (effectiveRules.length === 0) {
        console.log(`【促销管理器】${type}策略无生效规则,跳过执行`);
        return;
      }

      if (!strategy.isEffective(finalPrice)) {
        console.log(`【促销管理器】${type}策略对当前价格不生效,跳过执行`);
        return;
      }

      const prePrice = finalPrice;
      finalPrice = strategy.calculate(finalPrice);
      console.log(`【促销管理器】执行${type}策略:${prePrice.toFixed(2)}元 → ${finalPrice.toFixed(2)}元`);
    });

    finalPrice = Math.max(parseFloat(finalPrice.toFixed(2)), 0);
    console.log(`【促销管理器】最终价格:${finalPrice.toFixed(2)}元`);

    return finalPrice;
  }

  /** 设置促销策略执行顺序(自动去重) */
  public setExecuteOrder(order: PromotionType[]): void {
    const uniqueOrder = Array.from(new Set(order));
    this.executeOrder = uniqueOrder;
    console.log(`【促销管理器】叠加顺序已设置:${this.executeOrder.join('→')}`);
  }

  /** 获取已注册的促销策略类型 */
  public getRegisteredTypes(): PromotionType[] {
    return Array.from(this.strategyMap.keys());
  }

  /** 校验规则ID是否存在 */
  public hasRuleId(type: PromotionType, promotionId: string): boolean {
    return this.promotionRuleIds[type]?.includes(promotionId) ?? false;
  }

  /** 获取指定类型的规则ID列表(返回副本) */
  public getRuleIds(type: PromotionType): string[] {
    return [...(this.promotionRuleIds[type] || [])];
  }
}

六、商品实体层重构

6.1 抽象商品基类

// model/entity/AbstractGoods.ets
import { IBaseGoods } from '../interface/attribute/IBaseGoods';
import { GoodsStatus } from './GoodsStatus';
import { IPromotionRule } from '../interface/promotion/IPromotionRule';
import { PromotionType } from '../strategy/PromotionType';
import { PromotionManager } from '../utils/PromotionManager';
import { IDiscountRule } from '../interface/promotion/IDiscountRule';
import { IFullReductionRule, IReductionTier } from '../interface/promotion/IFullReductionRule';
import { IdGenerator } from '../utils/IdGenerator';

/**
 * 抽象商品基类:封装通用属性与促销入口
 * 核心:属性管理与促销逻辑解耦,子类仅扩展特有属性
 */
export abstract class AbstractGoods {
  public readonly goodsId: string;
  /** 每个商品独立持有促销管理器(非单例) */
  protected readonly promotionManager: PromotionManager;

  public name: string;
  public category: string = "商品分类";
  public status: GoodsStatus;
  protected costPrice: number;
  private _price: number;
  private _stock: number;

  constructor(props: IBaseGoods) {
    try {
      this.validateProps(props);
    } catch (error) {
      console.warn(error)
    }
    this.name = props.name.trim() || "未命名商品";
    this._price = Math.max(props.price, 0);
    this._stock = Math.max(props.stock, 0);
    this.costPrice = Math.max(props.costPrice, 0);
    this.category = props.category?.trim() || this.category;
    this.status = props.status ?? GoodsStatus.OFF_SHELF;
    this.goodsId = IdGenerator.generatePromotionId('goods_')
    console.log(`【商品初始化】${this.name} | 商品ID:${this.goodsId}`);
    this.promotionManager = new PromotionManager();
  }

  // 价格设置:非负校验
  set price(newPrice: number) {
    if (newPrice < 0) {
      console.warn(`【价格修改失败】${this.name} | 售价不能为负数`);
      return;
    }
    this._price = newPrice;
    console.log(`【价格修改成功】${this.name} | 原价更新为:${newPrice}元`);
  }

  get price(): number {
    return this._price;
  }

  // 库存设置:非负校验,库存为0自动下架
  set stock(num: number) {
    if (num < 0) {
      console.warn(`【库存修改失败】${this.name} | 库存不能为负数`);
      return;
    }
    this._stock = num;
    console.log(`【库存修改成功】${this.name} | 库存更新为:${num}件`);
    if (this._stock === 0) {
      this.status = GoodsStatus.OFF_SHELF;
      console.log(`【库存提醒】${this.name} | 库存为0,自动下架`);
    }
  }

  get stock(): number {
    return this._stock;
  }

  // 属性校验:非空+非负+库存与状态匹配
  private validateProps(props: IBaseGoods): void {
    if (!props.name || props.name.trim() === '') {
      throw new Error('【属性校验失败】商品名称不能为空');
    }
    if (props.price < 0 || props.stock < 0 || props.costPrice < 0) {
      throw new Error('【属性校验失败】价格、库存、成本价不能为负数');
    }
    if (props.status === GoodsStatus.ON_SHELF && props.stock === 0) {
      console.warn(`【属性校验提醒】${props.name}库存为0,强制改为下架状态`);
      props.status = GoodsStatus.OFF_SHELF;
    }
  }

  // 打印商品基础信息(含促销信息)
  public printBaseInfo(): void {
    const bestDiscountRule = this.promotionManager.getBestRule<IDiscountRule>(PromotionType.DISCOUNT, this.price);
    const discountInfo = bestDiscountRule
      ? `最优折扣:${bestDiscountRule.promotionName}(ID:${bestDiscountRule.promotionId},${bestDiscountRule.discountRate * 10}折)`
      : "无生效折扣规则";

    const bestFullReductionRule = this.promotionManager.getBestRule<IFullReductionRule>(PromotionType.FULL_REDUCTION, this.price);
    const formatTiers = (tiers: IReductionTier[]): string => {
      return tiers.map(t => `满${t.fullAmount}减${t.reductionAmount}`).join('、');
    };
    const fullReductionInfo = bestFullReductionRule
      ? `最优满减:${bestFullReductionRule.promotionName}(ID:${bestFullReductionRule.promotionId},档位:${formatTiers(bestFullReductionRule.tiers)})`
      : "无生效满减规则";

    const allRuleIds = `已添加规则ID:折扣[${this.promotionManager.getRuleIds(PromotionType.DISCOUNT).join(', ')}]、满减[${this.promotionManager.getRuleIds(PromotionType.FULL_REDUCTION).join(', ')}]`;

    console.log(`
    ===== 【${this.name}】商品基础信息 =====
    商品ID:${this.goodsId}
    商品分类:${this.category}
    商品原价:${this._price}元
    成本价格:${this.costPrice}元
    库存数量:${this._stock}件
    商品状态:${this.status === GoodsStatus.ON_SHELF ? '上架' : '下架'}
    最终售价:${this.calculateFinalPrice()}元
    单件利润:${this.calculateProfit()}元
    ===== 【${this.name}】商品促销信息 =====
    ${allRuleIds}
    ${discountInfo}
    ${fullReductionInfo}`);
  }

  // 添加促销规则(校验ID唯一性)
  public setPromotionRule<T extends IPromotionRule>(type: PromotionType, rule: T): boolean {
    if (this.promotionManager.hasRuleId(type, rule.promotionId)) {
      console.warn(`【${this.name}】${type}类型下已存在ID为${rule.promotionId}的规则`);
      return false;
    }
    const success = this.promotionManager.addPromotionRule(type, rule);
    if (success) {
      console.log(`【${this.name}】已配置${type}类型促销规则:${rule.promotionName}`);
    }
    return success;
  }

  // 获取指定类型的规则ID列表
  public getPromotionRuleIds(type: PromotionType): string[] {
    return this.promotionManager.getRuleIds(type);
  }

  // 获取指定类型的规则列表
  public getPromotionRules<T extends IPromotionRule>(type: PromotionType): T[] {
    return this.promotionManager.getPromotionRules<T>(type);
  }

  // 计算单件利润(非负)
  public calculateProfit(): number {
    const finalPrice = this.calculateFinalPrice();
    const profit = parseFloat((finalPrice - this.costPrice).toFixed(2));
    return Math.max(profit, 0);
  }

  // 全局ID查询规则
  public getPromotionRuleById<T extends IPromotionRule>(promotionId: string): T | undefined {
    return this.promotionManager.getPromotionRuleByGlobalId(promotionId)
  }

  // 按ID取消促销规则(校验ID存在性)
  public cancelPromotionRule(type: PromotionType, promotionId: string): boolean {
    if (!this.promotionManager.hasRuleId(type, promotionId)) {
      console.warn(`【${this.name}】${type}类型下无ID为${promotionId}的规则`);
      return false;
    }
    const success = this.promotionManager.cancelPromotionRule(type, promotionId);
    if (success) {
      console.log(`【${this.name}】已取消${type}类型ID为${promotionId}的规则`);
    }
    return success;
  }

  // 计算最终售价(委托给促销管理器)
  public calculateFinalPrice(): number {
    return this.promotionManager.calculateFinalPrice(this.price);
  }
}

6.2 图书商品类

// model/entity/BookGoods.ets
import { AbstractGoods } from './AbstractGoods';
import { IBookGoods } from '../interface/attribute/IBookGoods';

/** 图书商品:扩展作者、出版日期属性 */
export class BookGoods extends AbstractGoods {
  private _author: string;
  private _publishDate: string;

  constructor(props: IBookGoods) {
    super(props);
    this._author = props.author?.trim() || "未知作者";
    this._publishDate = props.publishDate?.trim() || "未知日期";
  }

  set author(newAuthor: string) {
    this._author = newAuthor.trim() || "未知作者";
  }

  get author(): string { return this._author; }

  set publishDate(newDate: string) {
    this._publishDate = newDate.trim() || "未知日期";
  }

  get publishDate(): string { return this._publishDate; }

  // 重写打印方法:补充图书专属信息
  public override printBaseInfo(): void {
    super.printBaseInfo()
    console.log(`
    【商户-图书详情信息补充】
    作者:${this._author}
    出版日期:${this._publishDate}`);
  }
}

6.3 数码商品类

// model/entity/DigitalGoods.ets
import { AbstractGoods } from './AbstractGoods';
import { IDigitalGoods } from '../interface/attribute/IDigitalGoods';
import { IReturnable } from '../interface/behavior/IReturnable';

/** 数码商品:扩展品牌、保修期属性,实现退换规则 */
export class DigitalGoods extends AbstractGoods implements IReturnable {
  private _brand: string;
  private _warranty: number;

  constructor(props: IDigitalGoods) {
    super(props);
    this._brand = props.brand.trim() || "未知品牌";
    this._warranty = Math.max(props.warranty, 1);
  }

  set brand(newBrand: string) {
    this._brand = newBrand.trim() || "未知品牌";
  }

  get brand(): string { return this._brand; }

  // 保修期设置:至少1年
  set warranty(newWarranty: number) {
    if (newWarranty < 1) {
      console.warn(`【${this.name}】保修期至少1年,修改失败`);
      return;
    }
    this._warranty = newWarranty;
  }

  get warranty(): number {
    return this._warranty;
  }

  // 重写打印方法:补充数码专属信息
  public override printBaseInfo(): void {
    super.printBaseInfo()
    console.log(`
    保修期:${this._warranty}年
    退换规则:${this.getReturnRule()}`);
  }

  // 实现退换接口:支持退换
  isSupportReturn(): boolean {
    return true;
  }

  // 实现退换接口:返回具体规则
  getReturnRule(): string {
    return `支持七天无理由退换,激活后不支持退换,享受${this._warranty}年官方保修服务`;
  }
}

七、促销演示入口

UI交互并不是我们关注的重点,重点验证我们写逻辑代码的正确性。

// pages/Index.ets
import { AbstractGoods } from '../model/entity/AbstractGoods';
import { BookGoods } from '../model/entity/BookGoods';
import { DigitalGoods } from '../model/entity/DigitalGoods';
import { GoodsStatus } from '../model/entity/GoodsStatus';
import { IDiscountRule } from '../model/interface/promotion/IDiscountRule';
import { IFullReductionRule } from '../model/interface/promotion/IFullReductionRule';
import { GoodsManager } from '../model/utils/GoodsManager';
import { PromotionType } from '../model/strategy/PromotionType';
import { IBookGoods } from '../model/interface/attribute/IBookGoods';
import { IDigitalGoods } from '../model/interface/attribute/IDigitalGoods';
import { IPromotionRule } from '../model/interface/promotion/IPromotionRule';

@Entry
@Component
struct Index {
  private goodsManager = GoodsManager.getInstance<AbstractGoods>();
  @State goodsList: AbstractGoods[] = [];

  aboutToAppear() {
    this.goodsManager.clearAllGoods();
    this.initGoodsData();
    this.goodsList = this.goodsManager.getGoodsList();
  }

  /** 初始化商品数据(图书+数码) */
  private initGoodsData(): void {
    // 1. 图书商品
    const bookProps: IBookGoods = {
      name: "ArkTS从入门到精通",
      price: 150,
      stock: 1000,
      costPrice: 50,
      category: "技术图书",
      status: GoodsStatus.ON_SHELF,
      author: "鸿蒙开发者",
      publishDate: "2025-01-01"
    };
    const book = new BookGoods(bookProps);

    // 图书折扣规则
    const bookDiscountRule: IDiscountRule = {
      promotionId: "discount_book_001",
      promotionName: "日常9折优惠",
      startTime: new Date("2025-01-01"),
      endTime: new Date("2026-12-31"),
      isEnabled: true,
      discountRate: 0.9,
      isMemberExclusive: false
    };
    book.setPromotionRule(PromotionType.DISCOUNT, bookDiscountRule);

    // 图书满减规则
    const bookFullRule: IFullReductionRule = {
      promotionId: "full_book_001",
      promotionName: "店铺满减",
      startTime: new Date("2025-01-01"),
      endTime: new Date("2026-12-31"),
      isEnabled: true,
      tiers: [{ fullAmount: 80, reductionAmount: 10 }, { fullAmount: 100, reductionAmount: 30 }]
    };
    book.setPromotionRule(PromotionType.FULL_REDUCTION, bookFullRule);

    // 2. 数码商品
    const phoneProps: IDigitalGoods = {
      name: "鸿蒙智能手机",
      price: 2000,
      stock: 500,
      costPrice: 1000,
      category: "数码产品",
      status: GoodsStatus.ON_SHELF,
      brand: "鸿蒙",
      warranty: 2
    };
    const phone = new DigitalGoods(phoneProps);

    // 数码折扣规则
    const phoneDiscountRule: IDiscountRule = {
      promotionId: "discount_phone_001",
      promotionName: "会员95折",
      startTime: new Date("2025-01-01"),
      endTime: new Date("2026-12-31"),
      isEnabled: true,
      discountRate: 0.95,
      isMemberExclusive: true
    };
    phone.setPromotionRule(PromotionType.DISCOUNT, phoneDiscountRule);

    this.goodsManager.addGoodsBatch([book, phone]);
  }

  /** 切换规则启用/禁用状态 */
  private toggleRule(goods: AbstractGoods,ruleId: string) {
    const rule = goods.getPromotionRuleById(ruleId);
    if (!rule) return;

    rule.isEnabled = !rule.isEnabled;
    this.goodsList = [...this.goodsList];

    const status = rule.isEnabled ? "开启" : "关闭";
    console.log(`✅ 商品【${goods.name}】的【${rule.promotionName}】已${status}`);
  }

  /** 格式化价格(保留两位小数) */
  private formatPrice(price: number): string {
    return price.toFixed(2);
  }

  /** 查看商品详情 */
  private viewDetail(goods: AbstractGoods) {
    goods.printBaseInfo();
  }

  build() {
    Column({ space: 20}) {
      Text("策略模式重构电商促销系统")
        .fontSize(22)
        .fontWeight(FontWeight.Bold);

      Scroll() {
        Column({ space: 15 }) {
          ForEach(this.goodsList, (goods: AbstractGoods) => {
            Column({ space: 10 })
              .width('90%')
              .backgroundColor(Color.White)
              .padding(15)
              .borderRadius(10)

              // 商品基础信息
              Row() {
                Text(goods.name).fontSize(18).flexGrow(1);
                Text(goods.status === GoodsStatus.ON_SHELF ? "在售" : "下架")
                  .fontSize(12)
                  .backgroundColor(goods.status === GoodsStatus.ON_SHELF ? "#E8F5E9" : "#FFEBEE")
                  .fontColor(goods.status === GoodsStatus.ON_SHELF ? "#4CAF50" : "#F44336")
                  .padding({ left: 8, right: 8, top: 2, bottom: 2 })
                  .borderRadius(4);
              }

              Text(`原价:¥${this.formatPrice(goods.price)} | 最终价:¥${this.formatPrice(goods.calculateFinalPrice())}`)
                .fontSize(14)
                .fontColor("#F44336");

              // 商品专属信息
              if (goods instanceof BookGoods) {
                Text(`作者:${goods.author} | 出版日期:${goods.publishDate}`)
                  .fontSize(12)
                  .fontColor("#999");
              }
              if (goods instanceof DigitalGoods) {
                Text(`品牌:${goods.brand} | 质保:${goods.warranty}年`)
                  .fontSize(12)
                  .fontColor("#999");
              }

              // 促销规则开关
              Column({ space: 8 }) {
                Text("促销规则开关").fontSize(14).fontWeight(FontWeight.Medium).width('100%');
                ForEach(goods.getPromotionRules(PromotionType.DISCOUNT),(rule:IPromotionRule) => {
                    Button(`${rule.isEnabled ? "关闭" : "开启"}【${rule.promotionName}】`)
                      .width('100%')
                      .height(36)
                      .fontSize(13)
                      .backgroundColor(rule.isEnabled ? "#F44336" : "#4CAF50")
                      .fontColor(Color.White)
                      .borderRadius(6)
                      .onClick(() => this.toggleRule(goods, rule.promotionId));
                  });

                ForEach(goods.getPromotionRules(PromotionType.FULL_REDUCTION),(rule:IPromotionRule) => {
                  Button(`${rule.isEnabled ? "关闭" : "开启"}【${rule.promotionName}】`)
                    .width('100%')
                    .height(36)
                    .fontSize(13)
                    .backgroundColor(rule.isEnabled ? "#F44336" : "#4CAF50")
                    .fontColor(Color.White)
                    .borderRadius(6)
                    .onClick(() => this.toggleRule(goods, rule.promotionId));
                });

              }
              .width('100%')
              .margin({ top: 10 })

              // 查看详情按钮
              Button("查看详情")
                .width('100%')
                .height(40)
                .backgroundColor("#2196F3")
                .fontColor(Color.White)
                .margin({ top: 10 })
                .onClick(() => this.viewDetail(goods));
            })
        }
      }
      .layoutWeight(1)
      .width('100%')
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f5f5f5')
    .padding(20);
  }
}

运行效果

策略模式重构电商促销系统

八、架构整合与工程化最佳实践

8.1 架构整合逻辑

重构后形成三层架构,职责清晰、依赖可控:

graph LR A[数据层:GoodsManager(单例)] -->|全局商品数据管控| B0[调用层:商品抽象类(AbstractGoods)] B0 -->|继承| B[调用层:商品类(Book/Digital)] C[调度层:PromotionManager(非单例)] -->|策略注册与叠加计算| B0 D[规则层:策略类(Discount/FullReduction)] -->|实现促销逻辑| C E[校验层:PromotionValidator] -->|规则合法性校验| D F[契约层:泛型接口+枚举] -->|约束策略行为| D

8.2 工程化最佳实践

  1. 策略模式应用:同一业务多实现方案时,用接口定义契约,封装独立策略类;策略类无状态,便于复用;
  2. 商户规则落地:规则ID外部化,取消规则置无效而非删除,同类型自动选最优;
  3. 类型安全:规则配置用泛型+枚举,避免运行时类型错误;
  4. 管理器设计:全局数据用单例,实例隔离逻辑用非单例;仅封装核心逻辑,剔除高危操作。

九、内容总结

核心收获

  1. 掌握策略模式落地方法,分离变化与不变逻辑,解决扩展性问题;
  2. 实现精细化促销管控,支持按ID精准取消、同类型选最优、不同类型叠加;
  3. 理解工程化设计思路,区分单例/非单例管理器,掌握泛型+枚举的类型安全用法;
  4. 提升代码可维护性,商品类与促销类职责单一,新增促销类型仅需扩展策略类。

关键点回顾

  1. 策略模式核心是接口契约+独立实现,解耦商品属性与促销计算;
  2. PromotionManager非单例设计,保证不同商品的规则隔离;
  3. 精准取消规则核心是修改状态而非删除,支持规则恢复;
  4. 规则ID外部传入是商户对账与追溯的关键需求。

十、代码仓库

十一、下节预告

本节基于策略模式完成了商品管理系统促销模块的重构,彻底解决了促销逻辑臃肿、价格计算分散、规则拓展困难等核心问题,也标志着这个轻量级商品管理系统的核心业务逻辑开发接近尾声。

而在真实的鸿蒙应用开发场景中,异步操作(如网络接口请求、本地数据读写、文件IO等)是绕不开的核心能力,同步编程模式下极易出现界面卡顿、逻辑嵌套混乱等问题。因此下一节将聚焦异步编程的核心——Promise异步并发,带你吃透异步开发的底层逻辑与实战技巧:

  1. 同步/异步执行差异拆解:结合"吃饭+玩手机"场景理解异步操作的必要性;
  2. 回调地狱痛点分析:演示传统回调方式"用户id + 用户详情"实现双接口请求的弊端;
  3. Promise核心规则:状态机制(pending/fulfilled/rejected)、链式调用、异常捕获;
  4. 异步并发实战:基于Promise.all实现多商品数据并行请求、Promise.race实现接口超时控制;
  5. 异步底层逻辑:ArkTS单线程模型+事件循环机制,理解异步代码的执行顺序。
posted @ 2026-01-25 11:05  鸿蒙-散修  阅读(0)  评论(0)    收藏  举报