零基础鸿蒙应用开发第二十七节:全局商品管理之单利模式

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

【学习目标】

  1. 掌握静态成员(静态属性/静态方法)核心特性,理解“类级复用”与“实例级复用”的本质区别,封装全局通用的商品工具能力;
  2. 深度理解单例模式底层逻辑(私有化构造器+全局唯一实例),基于泛型实现GoodsManager单例类,保障全应用商品数据唯一性;
  3. 整合“泛型+静态成员+单例模式”,构建“全局数据唯一+操作逻辑统一+类型安全”的商品管理体系;
  4. 解决应用商品数据多实例不同步问题,实现库存修改的全局实时同步;

【学习重点】

  • 静态核心:区分静态成员(类所有)与实例成员(对象所有)的内存分配,封装全局复用的商品校验/转换工具;
  • 单例实现:通过private constructor()+静态方法getInstance()确保GoodsManager全应用唯一;
  • 数据同步:单例的商品操作会同步到所有引用处,从根源解决多实例数据不一致;
  • 功能演示:通过“双实例对比+单例同步”的代码案例,直观验证单例模式的全局数据一致性;
  • 架构升级:以单例GoodsManager为核心,承接泛型容器能力,形成“单例中枢→多场景消费”的工程化架构。

一、工程结构

复制上一节的ClassObjectDemo_5工程,重命名为ClassObjectDemo_6,新增全局单例管理类,工程目录如下:

ClassObjectDemo_6/
├── entry/                          # 应用主模块
│   ├── src/main/ets/
│   │   ├── pages/                  # 视图层
│   │   │   └── Index.ets           # 核心演示页(单文件验证逻辑)
│   │   ├── model/                  # 核心模型层
│   │   │   ├── entity/             # 实体层(复用原有)
│   │   │   ├── interface/          # 契约层(复用原有)
│   │   │   ├── comparator/         # 比较器实现(复用原有)
│   │   │   └── utils/              # 工具层:新增静态/单例类
│   │   │       ├── GoodsList.ets   # 复用:上一节的泛型商品容器
│   │   │       ├── GoodsTool.ets   # 补充:静态工具方法
│   │   │       └── GoodsManager.ets # 新增:单例商品管理中枢
│   ├── resources/                  # 资源目录
│   └── module.json5                # 模块配置
└── hvigorfile.ts                   # 构建脚本

二、核心痛点回顾与技术选型

2.1 多实例数据不一致的本质问题

上一节实现的GoodsList泛型容器,每次new都会创建独立实例:

// 实例A(模拟页面1)
const goodsContainerA = new GoodsList<AbstractGoods>(); 
// 实例B(模拟页面2)
const goodsContainerB = new GoodsList<AbstractGoods>();
  • 内存中存在多份独立的商品数据,修改实例A的库存,实例B数据无变化;
  • 重复编写增删查改逻辑,代码冗余且数据状态混乱。

2.2 技术选型:静态成员+单例模式

技术特性 解决的核心问题 适用场景
静态成员 工具方法/常量的全局复用,无需创建实例 商品校验、格式转换、常量定义
单例模式 保证类实例唯一,数据全局共享 商品数据的统一管理
泛型 保障商品类型安全,适配所有商品子类 单例类的类型约束

三、静态成员:全局复用的工具能力

3.1 静态成员核心逻辑

  • 静态属性/方法:属于类本身,内存仅一份,通过类名.属性/方法直接调用;
  • 实例属性/方法:属于每个实例对象,每个实例有独立副本,需通过实例名调用;
  • 核心优势:工具类无需实例化,全局复用无冗余;
  • 修饰符:通过static修饰。

3.2 重构GoodsTool静态工具类

// model/utils/GoodsTool.ets
import { AbstractGoods } from '../entity/AbstractGoods';
import { IGoodsComparator } from '../interface/behavior/IGoodsComparator';

/**
 * 通用商品工具类(静态成员实现)
 * 核心:全局复用,无需创建实例,封装无状态工具能力
 */
export class GoodsTool {
  // 新增:全局常量:价格/库存阈值(静态属性)
  public static readonly MIN_PRICE = 0;
  public static readonly MAX_STOCK = 9999;

  // 静态方法:校验库存合法性
  public static validateStock(stock: number): boolean {
    return typeof stock === 'number' && stock >= 0 && stock <= GoodsTool.MAX_STOCK;
  }

  // 静态方法:校验商品完整性(适配抽象类自动生成的goodsId)
  public static validateGoods<T extends AbstractGoods>(goods: T): boolean {
    if (!goods) return false;
    const hasBasicProps = !!goods.name && !!goods.category;
    const hasValidPrice = typeof goods.price === 'number' && goods.price >= GoodsTool.MIN_PRICE;
    const hasValidStock = GoodsTool.validateStock(goods.stock);
    return hasBasicProps && hasValidPrice && hasValidStock;
  }

  // 静态方法:价格格式化
  public static formatPrice(price: number): string {
    if (typeof price !== 'number' || price < GoodsTool.MIN_PRICE) return '¥0.00';
    return `¥${price.toFixed(2)}`;
  }

  /** 筛选有库存商品 */
  static filterInStock<T extends AbstractGoods>(goodsList: T[]): T[] {
    if (!Array.isArray(goodsList)) return [];
    return goodsList.filter(g => g && g.stock > 0);
  }

  /** 价格区间筛选 */
  static filterByPriceRange<T extends AbstractGoods>(goodsList: T[], minPrice: number, maxPrice: number): T[] {
    if (!Array.isArray(goodsList) || minPrice > maxPrice) return [];
    const validMin = Math.max(minPrice, 0);
    const validMax = Math.max(maxPrice, 0);
    return goodsList.filter(g => g && g.price >= validMin && g.price <= validMax);
  }

  /** 自定义比较器排序(返回副本,不修改原数据) */
  static sortByComparator<T extends AbstractGoods>(goodsList: T[], comparator: IGoodsComparator<T>): T[] {
    const validGoods = goodsList.filter(g => g);
    return [...validGoods].sort((a, b) => comparator.compare(a, b));
  }
}

3.3 静态成员调用示例

// 调用静态方法
const isValidStock = GoodsTool.validateStock(100); // 输出:true
const priceStr = GoodsTool.formatPrice(5999); // 输出:¥5999.00

// 访问静态属性
console.log(GoodsTool.MAX_STOCK); // 输出:9999

四、单例模式:全局唯一的商品管理中枢

4.1 单例模式核心实现逻辑

目标:保证类仅有一个实例,提供全局访问点,核心步骤:

  1. 私有化构造器:private constructor() 禁止外部new创建实例;
  2. 私有静态属性:存储唯一实例(初始为null);
  3. 公共静态方法:getInstance() 提供全局访问入口(懒加载创建实例)。

4.2 实现GoodsManager单例类

// model/utils/GoodsManager.ets
import { AbstractGoods } from '../entity/AbstractGoods';
import { GoodsList } from './GoodsList';
import { GoodsTool } from './GoodsTool';

/**
 * 商品管理单例类(泛型+单例模式)
 * 核心:全应用仅一个实例,统一管理商品数据,代理GoodsList全部能力
 */
export class GoodsManager<T extends AbstractGoods> {
  // 1. 私有静态属性:存储唯一实例
  private static instance: GoodsManager<AbstractGoods> | null = null;
  // 2. 私有实例属性:复用上一节的泛型商品容器
  private readonly goodsContainer: GoodsList<AbstractGoods>;

  // 3. 私有化构造器:禁止外部创建实例
  private constructor() {
    this.goodsContainer = new GoodsList<AbstractGoods>();
    console.log("【GoodsManager】单例实例初始化完成");
  }

  // 4. 公共静态方法:全局唯一访问入口(懒加载+泛型适配)
  public static getInstance<T extends AbstractGoods>(): GoodsManager<T> {
    if (!GoodsManager.instance) {
      GoodsManager.instance = new GoodsManager<AbstractGoods>();
    }
    return GoodsManager.instance as GoodsManager<T>;
  }

  /** 添加商品(带合法性校验) */
  public addGoods(goods: T): boolean {
    if (!GoodsTool.validateGoods(goods)) {
      console.log(`【单例】添加失败:${goods.name}信息不合法`);
      return false;
    }
    const result = this.goodsContainer.addGoods(goods);
    result && console.log(`【单例】成功添加商品:${goods.name}(ID: ${goods.goodsId})`);
    return result;
  }

  /** 修改商品库存(带合法性校验) */
  public updateGoodsStock(id: string, newStock: number): boolean {
    if (!GoodsTool.validateStock(newStock)) {
      console.log(`【单例】修改失败:库存${newStock}不合法(范围0-${GoodsTool.MAX_STOCK})`);
      return false;
    }
    const result = this.goodsContainer.updateGoodsStock(id, newStock);
    if (result) {
      console.log(`【单例】库存修改成功,新库存:${newStock}`);
    } else {
      console.log(`【单例】修改失败:未找到goodsId=${id}的商品`);
    }
    return result;
  }

  /** 获取完整商品列表(返回副本,避免外部篡改) */
  public getGoodsList(): T[] {
    return this.goodsContainer.getGoodsList() as T[];
  }

  /** 获取商品总数 */
  public getCount(): number {
    return this.goodsContainer.getCount();
  }

  /** 删除商品 */
  public removeGoodsById(id: string): boolean {
    const result = this.goodsContainer.removeGoodsById(id);
    if (result) {
      console.log(`【单例】成功删除goodsId=${id}的商品`);
    } else {
      console.log(`【单例】删除失败:未找到goodsId=${id}的商品`);
    }
    return result;
  }

  /** 批量添加商品 */
  public addGoodsBatch(goodsList: T[]): number {
    // 批量添加前先过滤非法商品(复用GoodsTool校验)
    const validGoods = goodsList.filter(goods => GoodsTool.validateGoods(goods));
    const count = this.goodsContainer.addGoodsBatch(validGoods as AbstractGoods[]);
    console.log(`【单例】批量添加${count}个商品成功(过滤${goodsList.length - count}个非法商品)`);
    return count;
  }

  /**
   * 按商品名称模糊查询(不区分大小写,返回副本避免篡改)
   * @param name 商品名称(支持模糊匹配)
   * @returns 匹配的商品列表
   */
  public getGoodsByName(name: string): T[] {
    const matchedGoods = this.goodsContainer.getGoodsByName(name) ;
    console.log(`【单例】根据名称"${name}"查询到${matchedGoods.length}个商品`);
    return matchedGoods as T[];
  }

  /**
   * 按商品ID精准查询(返回副本避免篡改)
   * @param id 商品唯一ID
   * @returns 匹配的商品(副本),未找到返回undefined
   */
  public getGoodsById(id: string): T | undefined {
    const matchedGoods = this.goodsContainer.getGoodsById(id) as T | undefined;
    if (matchedGoods) {
      console.log(`【单例】根据ID"${id}"查询到商品:${matchedGoods.name}`);
    } else {
      console.log(`【单例】根据ID"${id}"未查询到商品`);
    }
    // 返回副本,避免外部修改内部数据
    return matchedGoods;
  }

  /**
   * 按商品分类精准筛选(返回副本避免篡改)
   * @param category 商品分类(自动去除首尾空格)
   * @returns 匹配的商品列表
   */
  public getGoodsByCategory(category: string): T[] {
    const matchedGoods = this.goodsContainer.getGoodsByCategory(category) as T[];
    console.log(`【单例】根据分类"${category.trim()}"查询到${matchedGoods.length}个商品`);
    return matchedGoods;
  }

  /**
   * 清空所有商品(全应用统一清空)
   */
  public clearAllGoods(): void {
    const beforeCount = this.getCount();
    this.goodsContainer.clear();
    console.log(`【单例】成功清空所有商品,共清空${beforeCount}个商品`);
  }
}

4.3 单例模式关键细节

  • 懒加载:首次调用getInstance()才创建实例,减少应用启动内存占用;
  • 私有化构造器:杜绝外部new实例,从语法层面保证实例唯一性;
  • 全局统一:任何位置调用getInstance()都获取同一个实例,操作同一套数据;
  • 逻辑复用:代理上一节GoodsList的增删改查能力,无需重复编码;
  • 泛型适配getInstance<T>()支持泛型,适配不同类型的商品子类。

五、工程演示(验证单例数据同步)

5.1 核心演示代码

// pages/Index.ets
import { AbstractGoods } from '../model/entity/AbstractGoods';
import { DigitalGoods } from '../model/entity/DigitalGoods';
import { GoodsStatus } from '../model/entity/GoodsStatus';
import { IDigitalGoods } from '../model/interface/attribute/IDigitalGoods';
import { GoodsList } from '../model/utils/GoodsList';
import { GoodsManager } from '../model/utils/GoodsManager';

@Entry
@Component
struct Index {
  @State goodsList: AbstractGoods[] = [];
  @State logContent: string[] = []; // 页面日志展示
  private goodsManager = GoodsManager.getInstance<AbstractGoods>(); // 单例引用

  aboutToAppear() {
    this.initSingletonData();
    this.logContent.push("=== 日志展示区 ===");
    this.logContent.push("点击下方按钮查看演示日志");
  }

  build() {
    Column() {
      Text("静态成员与单例模式演示")
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin(20);

      Button("演示多实例数据不一致")
        .onClick(() => this.demoMultiInstance())
        .margin(10);

      Button("演示单例模式数据同步")
        .onClick(() => this.demoSingleInstance())
        .margin(10);

      Button("清空日志")
        .onClick(() => this.logContent = ["=== 日志展示区 ==="])
        .margin(10)
        .backgroundColor(Color.Grey);

      // 日志展示区域
      Scroll() {
        Column({ space: 8 }) {
          ForEach(this.logContent, (log: string) => {
            Text(log)
              .fontSize(14)
              .fontColor('#333')
              .width('100%');
          })
        }.padding(10)
      }
      .layoutWeight(1)
      .width('90%')
      .backgroundColor('#fff')
      .borderRadius(8)
      .margin(10);
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f5f5f5')
    .padding(10);
  }

  /** 初始化单例商品数据(适配抽象类自动生成goodsId) */
  private initSingletonData(): void {
    // 数码商品(无需手动赋值goodsId)
    const digitalProps:IDigitalGoods = {
      name: "鸿蒙Mate70手机",
      price: 5999,
      stock: 100,
      costPrice: 4000,
      category: "数码产品",
      status: GoodsStatus.ON_SHELF,
      brand: "华为",
      warranty: 2
    };

    const tabletProps: IDigitalGoods = {
      name: "鸿蒙平板Pro",
      price: 3999,
      stock: 80,
      costPrice: 2500,
      category: "数码产品",
      status: GoodsStatus.ON_SHELF,
      brand: "华为",
      warranty: 2
    };

    this.goodsManager.addGoodsBatch([
      new DigitalGoods(digitalProps),
      new DigitalGoods(tabletProps),
    ]);
    this.goodsList = this.goodsManager.getGoodsList();
  }

  /** 自定义日志方法(控制台+页面双展示) */
  private addLog(logText: string): void {
    console.log(logText);
    this.logContent.push(logText);
    this.logContent = [...this.logContent]; // 触发UI刷新
  }

  /** 演示1:多实例数据不一致 */
  private demoMultiInstance(): void {
    this.addLog("\n--- 多实例数据不一致演示 ---");
    const containerA = new GoodsList<AbstractGoods>();
    const containerB = new GoodsList<AbstractGoods>();

    // 创建测试商品
    const testGoods = new DigitalGoods({
      name: "鸿蒙平板Pro",
      price: 3999,
      stock: 100,
      costPrice: 2500,
      category: "数码产品",
      status: GoodsStatus.ON_SHELF,
      brand: "华为",
      warranty: 2
    });
    containerA.addGoods(testGoods);

    // 输出对比结果
    this.addLog("实例A商品总数:" + containerA.getCount()); // 1
    this.addLog("实例B商品总数:" + containerB.getCount()); // 0
    containerA.updateGoodsStock(testGoods.goodsId, 50);
    this.addLog("实例A商品库存:" + containerA.getGoodsList()[0]?.stock); // 50
    this.addLog("实例B商品库存:" + containerB.getGoodsList()[0]?.stock ?? "无"); // 无
    this.addLog("===============================");
  }

  /** 演示2:单例模式数据同步 */
  private demoSingleInstance(): void {
    this.addLog("\n--- 单例模式数据同步演示 ---");
    const managerA = GoodsManager.getInstance<AbstractGoods>();
    const managerB = GoodsManager.getInstance<AbstractGoods>();

    // 验证实例唯一性
    this.addLog("实例是否唯一:" + (managerA === managerB)); // true

    // 新增商品
    const testGoods = new DigitalGoods({
      name: "鸿蒙电脑",
      price: 5999,
      stock: 100,
      costPrice: 4000,
      category: "数码产品",
      status: GoodsStatus.ON_SHELF,
      brand: "华为",
      warranty: 2
    });
    managerA.addGoods(testGoods);

    // 总数对比
    this.addLog("管理器A商品总数:" + managerA.getCount()); // 3(初始化2+新增1)
    this.addLog("管理器B商品总数:" + managerB.getCount()); // 3

    // 修改库存并验证同步
    managerA.updateGoodsStock(testGoods.goodsId, 50);
    this.addLog("管理器A商品库存:" + managerA.getGoodsList().find(g => g.goodsId === testGoods.goodsId)?.stock); // 50
    this.addLog("管理器B商品库存:" + managerB.getGoodsList().find(g => g.goodsId === testGoods.goodsId)?.stock); // 50

    // 反向修改验证
    managerB.updateGoodsStock(testGoods.goodsId, 30);
    this.addLog("管理器A库存(B修改后):" + managerA.getGoodsList().find(g => g.goodsId === testGoods.goodsId)?.stock); // 30
    this.addLog("管理器B库存(B修改后):" + managerB.getGoodsList().find(g => g.goodsId === testGoods.goodsId)?.stock); // 30
    this.addLog("===============================");
  }
}

5.2 演示结果

点击“演示多实例数据不一致”:

--- 多实例数据不一致演示 ---
实例A商品总数:1
实例B商品总数:0
实例A商品库存:50
实例B商品库存:无
===============================

点击“演示单例模式数据同步”:

--- 单例模式数据同步演示 ---
实例是否唯一:true
【单例】成功添加商品:鸿蒙电脑(ID: goods_1736300000_1234)
管理器A商品总数:3
管理器B商品总数:3
【单例】库存修改成功,新库存:50
管理器A商品库存:50
管理器B商品库存:50
【单例】库存修改成功,新库存:30
管理器A库存(B修改后):30
管理器B库存(B修改后):30
===============================

运行效果

单利模式_20260108134312_615_125

六、单例模式工程化最佳实践

6.1 避坑指南

  1. 禁止继承:单例类通过private constructor杜绝子类继承,避免破坏单例特性;
  2. 线程安全:ArkTS主线程为单线程模型,private constructor + 懒加载getInstance()天然保证实例唯一,无需加锁;若涉及TaskPool/Worker异步场景,需注意线程间内存隔离;
  3. 轻量化:单例仅存储核心数据(如商品列表),页面临时状态不放入,避免内存泄漏;
  4. 懒加载优先:首次调用getInstance()创建实例,减少应用启动耗时。

6.2 静态成员 vs 单例模式

特性 静态成员 单例模式
调用方式 类名.属性/方法 类名.getInstance()获取实例后调用
数据类型 无状态(常量/工具方法) 有状态(全局共享数据)
核心用途 全局工具能力复用 全局数据状态管理
扩展性 无法继承扩展 可通过接口扩展能力

七、架构整合逻辑

graph LR A[属性契约层<br/>定义商品基础属性] --> B[抽象类层<br/>自动生成goodsId+封装通用方法] C[行为契约层<br/>排序/促销接口规范] --> B B --> D[泛型约束层<br/>T extends AbstractGoods] D --> E[静态工具类:全局校验/格式化] D --> F[单例管理类:全局商品数据管控] E --> F[单例复用静态校验能力] F --> G[多页面/组件<br/>共享统一商品数据]

八、内容总结

  1. 静态成员核心:静态属性/方法属于类本身,内存仅一份,适合封装全局无状态工具能力(如校验、格式化),无需实例化即可调用;
  2. 多实例问题本质:多个GoodsList实例数据相互隔离,修改操作无法同步,导致数据不一致;
  3. 单例模式核心价值:通过private constructor()+getInstance()保证实例唯一,所有操作基于同一套数据,从根源解决全局数据同步问题;
  4. 工程化整合:单例类代理上一节泛型容器的核心能力,静态类提供全局工具,结合泛型保障类型安全,形成“数据唯一+操作统一”的商品管理体系。

九、代码仓库

十、下节预告

本节课通过静态成员+单例模式解决了多实例数据不一致问题,但商品排序体系仍有优化空间:排序规则与页面强耦合、新增规则需修改页面代码。下一节将聚焦工厂模式+策略模式

  1. 基于IGoodsComparator接口落地策略模式,新增排序规则仅需实现接口,遵循开闭原则;
  2. 实现排序工厂类,页面通过排序类型即可获取比较器,屏蔽底层实现细节;
  3. 整合“单例管控+策略+工厂”,让排序逻辑灵活扩展、低耦合。
posted @ 2026-01-25 11:01  鸿蒙-散修  阅读(2)  评论(0)    收藏  举报