零基础鸿蒙应用开发第二十九节:策略模式重构电商促销系统
【学习目标】
- 掌握策略模式核心思想,实现电商促销场景下“商品属性”与“促销计算”的职责分离;
- 解决传统促销系统类臃肿、拓展难、叠加逻辑混乱等核心痛点;
- 理解
PromotionManager非单例设计逻辑,落地“规则池+精准ID管控”的商户运营需求; - 掌握泛型策略接口与枚举的组合用法,保障代码编译期类型安全;
- 实现“同类型选最优、不同类型叠加、按ID精准取消”的促销规则逻辑;
- 理解ArkTS工程化设计原则,区分单例/非单例管理器的适用场景。
【学习重点】
- 核心解耦:将促销计算逻辑从商品类抽离为独立策略类,商品类仅负责属性管理;
- 规则管控:外部传入规则ID、按ID置规则无效(非删除)、同类型规则自动选最优;
- 管理器设计:
PromotionManager非单例实现商品规则隔离,GoodsManager单例保障全局商品数据一致; - 类型安全:通过
IPromotionStrategy<T>泛型接口+PromotionType枚举,避免硬编码与类型错误; - 场景适配:落地多档位满减校验、规则叠加顺序配置、价格非负兜底等商户实际需求。
一、工程结构
复制工程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 |
| 叠加逻辑僵化 | 多规则执行顺序固定在商品类中,无法灵活适配商户“先满减后折扣”等需求 |
| 复用性差 | 不同商品的同类促销规则无法复用,重复代码大量滋生 |
| 管控粒度粗糙 | 仅支持整类促销取消,无法精准暂停单条活动,易引发运营误操作 |
三、核心设计思路
基于策略模式重构,核心思路为职责分离、契约约束、统一管控:
- 职责分离:将促销计算逻辑抽离为独立策略类,商品类仅管理自身属性,促销管理器负责策略调度;
- 策略类设计:折扣/满减策略类各自实现促销逻辑,通过泛型接口约束行为一致性;
- 商品抽象类设计:每个商品实例独立持有促销管理器,实现规则隔离;封装属性校验、价格/利润计算等通用能力,子类仅需扩展特有属性;
- 商户规则落地:支持同类型规则选最优、多档位满减匹配、按ID精准取消(置无效而非删除)、规则ID外部传入保证可追溯;
- 类型安全约束:通过泛型策略接口限定规则类型,枚举统一管理促销类型,避免硬编码错误。
- 可拓展性:新增促销类型(如优惠券、拼团折扣)仅需实现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 工程化最佳实践
- 策略模式应用:同一业务多实现方案时,用接口定义契约,封装独立策略类;策略类无状态,便于复用;
- 商户规则落地:规则ID外部化,取消规则置无效而非删除,同类型自动选最优;
- 类型安全:规则配置用泛型+枚举,避免运行时类型错误;
- 管理器设计:全局数据用单例,实例隔离逻辑用非单例;仅封装核心逻辑,剔除高危操作。
九、内容总结
核心收获
- 掌握策略模式落地方法,分离变化与不变逻辑,解决扩展性问题;
- 实现精细化促销管控,支持按ID精准取消、同类型选最优、不同类型叠加;
- 理解工程化设计思路,区分单例/非单例管理器,掌握泛型+枚举的类型安全用法;
- 提升代码可维护性,商品类与促销类职责单一,新增促销类型仅需扩展策略类。
关键点回顾
- 策略模式核心是接口契约+独立实现,解耦商品属性与促销计算;
PromotionManager非单例设计,保证不同商品的规则隔离;- 精准取消规则核心是修改状态而非删除,支持规则恢复;
- 规则ID外部传入是商户对账与追溯的关键需求。
十、代码仓库
- 工程名称:
ClassObjectDemo_8 - 仓库地址:https://gitee.com/juhetianxia321/harmony-os-code-base.git
十一、下节预告
本节基于策略模式完成了商品管理系统促销模块的重构,彻底解决了促销逻辑臃肿、价格计算分散、规则拓展困难等核心问题,也标志着这个轻量级商品管理系统的核心业务逻辑开发接近尾声。
而在真实的鸿蒙应用开发场景中,异步操作(如网络接口请求、本地数据读写、文件IO等)是绕不开的核心能力,同步编程模式下极易出现界面卡顿、逻辑嵌套混乱等问题。因此下一节将聚焦异步编程的核心——Promise异步并发,带你吃透异步开发的底层逻辑与实战技巧:
- 同步/异步执行差异拆解:结合"吃饭+玩手机"场景理解异步操作的必要性;
- 回调地狱痛点分析:演示传统回调方式"用户id + 用户详情"实现双接口请求的弊端;
- Promise核心规则:状态机制(pending/fulfilled/rejected)、链式调用、异常捕获;
- 异步并发实战:基于
Promise.all实现多商品数据并行请求、Promise.race实现接口超时控制; - 异步底层逻辑:ArkTS单线程模型+事件循环机制,理解异步代码的执行顺序。
浙公网安备 33010602011771号