散修带你入门鸿蒙应用开发基础第十三节:方法重写与抽象类应用

第十三节:方法重写与抽象类应用

炼气十三重天

【学习目标】

  1. 掌握override关键字的显式重写语法,规范子类方法重写行为,提升代码可读性与可维护性;
  2. 理解抽象类与抽象方法的核心意义,掌握abstract关键字的使用规则;
  3. 实现基于抽象类的商品规范约束,强制子类实现核心业务逻辑,规避“空实现”导致的业务异常;
  4. 结合鸿蒙电商场景,完成多态进阶实战,实现更严格的商品类型统一管理;
  5. 区分抽象类与普通类的适用场景,理解抽象类在大型鸿蒙工程中的架构价值。

【学习重点】

  • override显式重写的语法规范与工程意义;
  • 抽象类/抽象方法的定义规则(abstract关键字、无方法体、子类强制实现);
  • 抽象类作为“规范父类”在鸿蒙电商商品管理中的落地;
  • 抽象类与多态结合的进阶用法,实现子类逻辑的强制统一;
  • 抽象类与普通类、接口的核心区别。

一、工程结构

复制第十二节的ClassObjectDemo_1工程,重命名为ClassObjectDemo_2,新增抽象商品类及秒杀商品子类,核心目录如下:

ClassObjectDemo_2/
├── entry/                          # 应用主模块
│   ├── src/main/ets/
│   │   ├── pages/
│   │   │   └── Index.ets           # 测试页面(抽象类+多态调用)
│   │   └── model/
│   │   │   ├── AbstractGoods.ets   # 抽象父类:商品规范类(核心)
│   │   │   ├── PhysicalGoods.ets   # 子类:实体商品(实现抽象方法)
│   │   │   ├── VirtualGoods.ets    # 子类:虚拟商品(实现抽象方法)
│   │   │   └── SeckillGoods.ets    # 新增子类:秒杀商品(扩展抽象类)
│   ├── resources/                  # 资源目录(复用)
│   └── module.json5                # 模块配置(复用)
└── hvigorfile.ts                   # 构建脚本(复用)

二、方法重写规范:override关键字

2.1 隐式重写的问题

第十二节中子类重写父类方法时,仅通过“方法名、参数、返回值一致”实现隐式重写,存在以下核心隐患:

  1. 可读性差:外部开发者无法快速识别重写方法,需逐行追溯父类代码确认;
  2. 维护性低:父类方法签名变更(如参数/返回值修改)时,子类隐式重写方法会“失效”且无编译提示;
  3. 协作混乱:多人开发时,无法区分“子类新增方法”和“重写父类方法”,易引发逻辑冲突。

隐式重写的隐患示例

// 父类Goods 示例演示
export class Goods {
  public profitRate: number = 0.1; // 默认10%利润率
  protected _basePrice: number;

  constructor(name: string, basePrice: number) {
    this._basePrice = basePrice;
  }

  calculateSellingPrice(): number {
    return this._basePrice * (1 + this.profitRate); 
  }
}

// 子类VirtualGoods(隐式重写)
export class VirtualGoods extends Goods {
  // 无标识,无法直接判断是重写还是新增方法
  calculateSellingPrice(): number {
    this.profitRate = 0.15;
    return this._basePrice * (1 + this.profitRate);
  }

  // 若父类后续将方法名改为calculatePrice,此处会变成子类新增方法,逻辑异常
}

2.2 override显式重写语法

ArkTS提供override关键字标记子类的重写方法,核心规则如下:

  1. 子类重写父类public/protected方法时,建议显式添加override,明确标注重写关系;
  2. override方法未匹配父类方法签名(方法名/参数/返回值不一致),编译器直接报错,强制保证重写正确性;
  3. override不能用于重写父类private方法(子类无法访问private方法,无重写基础)。

结合十二节protected修饰符的使用,override重写的方法仅能基于父类public/protected方法实现,无法重写private方法,这是继承场景下“访问控制+方法重写”的双重规范。

显式重写实战(改造虚拟商品类)

// model/VirtualGoods.ets(基于第十二节代码改造,后续会整合到抽象类体系中)
import { Goods } from './Goods';

export class VirtualGoods extends Goods {
  // 虚拟商品专属属性:有效期(天)
  public validDays: number;
  // 虚拟商品专属属性:是否自动续费
  public isAutoRenew: boolean;

  /**
   * 构造函数:初始化虚拟商品属性
   * @param name 商品名称
   * @param basePrice 商品底价
   * @param validDays 有效期(默认30天)
   * @param isAutoRenew 是否自动续费
   * @param category 商品分类(可选)
   */
  constructor(
    name: string,
    basePrice: number,
    validDays: number,
    isAutoRenew?: boolean,
    category?: string,
    profitRate: number = 0.15 // 虚拟商品默认15%利润率 (有默认值默认放在最后)
  ) {
    super(name, basePrice);
    this.profitRate = profitRate;
    this.validDays = validDays < 1 ? 30 : validDays;
    this.isAutoRenew = isAutoRenew ?? false;
  }

  // 显式标记重写父类方法,编译器校验签名一致性
  override calculateSellingPrice(): number {
    return Number((this._basePrice * (1 + this.profitRate)).toFixed(2));
  }

  // 显式标记重写父类打印方法
  override printBaseInfo(): void {
    super.printBaseInfo(); // 调用父类方法使用`super`
    console.log(`
      有效期:${this.validDays}天
      自动续费:${this.isAutoRenew ? "开启" : "关闭"}
      利润率:${this.profitRate * 100}%
    `);
  }

  // 个性方法:延长有效期
  extendValidity(days: number): void {
    if (days <= 0) {
      console.log("有效期延长失败:天数不合法");
      return;
    }
    this.validDays += days;
    console.log(`有效期延长${days}天,总计${this.validDays}天`);
  }
}

2.3 override核心价值对比

特性 隐式重写 显式重写(override)
代码可读性 需追溯父类确认重写关系 见名知意,直接标记重写方法
编译校验 无校验,父类方法变更易失效 强制校验方法签名,保证重写有效性
工程规范性 无统一标准,协作易混乱 符合鸿蒙工程编码规范,便于维护
调试效率 需对比父子类代码定位问题 快速定位重写方法,调试更高效

三、抽象类与抽象方法:强制子类实现核心逻辑

3.1 为什么需要抽象类?

第十二节的Goods普通类提供了calculateSellingPrice默认实现,但电商业务中:

  • 实体商品、虚拟商品、秒杀商品的定价规则存在明显差异;
  • 若子类未重写calculateSellingPrice,会直接复用父类默认逻辑,导致不同商品的定价规则错误;
  • 无法强制要求子类实现个性化的核心方法,存在“空实现”“默认实现”的致命隐患。

抽象类核心价值:定义子类必须遵循的“规范接口”,强制子类实现核心业务方法,同时封装通用逻辑实现复用,兼顾“规范约束”与“代码复用”。

3.2 抽象类核心规则

  1. 抽象类定义:用abstract关键字修饰类,抽象类不能直接实例化,仅能作为父类被继承;
  2. 抽象方法定义:抽象类中用abstract修饰的方法无方法体,仅定义方法签名(方法名+参数+返回值);
  3. 子类强制实现:继承抽象类的子类必须实现父类所有抽象方法,否则编译报错;
  4. 混合成员:抽象类可同时包含抽象方法(强制子类实现)和普通方法/属性(提供通用逻辑复用)。

3.3 实战:定义抽象商品类

将通用商品类Goods改造为抽象类AbstractGoods,强制子类实现售价计算和库存校验的核心业务逻辑:

// model/AbstractGoods.ets(抽象父类:商品规范类)
export abstract class AbstractGoods {
  public name: string;
  public category: string = "商品分类";
  protected _basePrice: number; // 改为protected,子类可访问
  public profitRate: number; // 利润率(默认10%,子类可继承/修改)

  constructor(
    name: string,
    basePrice: number,
    category?: string,
    profitRate: number = 0.1 // 默认10%利润率,与前节保持一致
  ) {
    this.name = name;
    this._basePrice = basePrice > 0 ? basePrice : 0; // 底价非负校验
    this.profitRate = profitRate > 0 ? profitRate : 0.1; // 利润率非负校验,默认10%
    this.category = category?.trim() || this.category;
  }

  // getter:受控读取底价
  get basePrice() {
    return this._basePrice;
  }

  // setter:受控修改底价
  set basePrice(newPrice: number) {
    if (newPrice < 0) {
      console.log("商品底价不能为负");
      return;
    }
    this._basePrice = newPrice;
  }
  
  // 共性方法:打印基础信息
  printBaseInfo(): void {
    console.log(`
      商品名称:${this.name}
      商品分类:${this.category}
      商品底价:${this._basePrice}元
      利润率:${this.profitRate * 100}%
    `);
  }

  // 抽象方法1:强制子类实现售价计算
  // 抽象方法默认public,不可用private修饰(子类无法实现)
  abstract calculateSellingPrice(): number;

  // 抽象方法2:强制子类实现库存校验(实体/虚拟/秒杀商品校验逻辑差异大)
  abstract checkStock(amount: number): boolean;
}

3.4 子类实现抽象方法(强制约束)

1. 改造实体商品类(实现抽象方法,优化参数顺序)

// model/PhysicalGoods.ets
import { AbstractGoods } from './AbstractGoods';

export class PhysicalGoods extends AbstractGoods {
  public stock: number; // 个性属性:实体库存
  public weight: number; // 个性属性:商品重量(kg)

  /**
   * 构造函数:优化参数顺序(必选参数 → 有默认值参数,按常用度排序)
   * @param name 商品名称
   * @param basePrice 商品底价
   * @param stock 库存数量
   * @param weight 商品重量
   * @param category 商品分类(默认:实体商品)
   * @param profitRate 利润率(默认:0.1)
   */
  constructor(
    name: string,
    basePrice: number,
    stock: number,
    weight: number,
    category: string = "实体商品", // 有默认值:常用可选参数在前
    profitRate: number = 0.1 // 有默认值:不常用可选参数在后
  ) {
    super(name, basePrice, category, profitRate); // 调用抽象父类构造函数
    this.stock = stock < 0 ? 0 : stock; // 库存非负校验
    this.weight = weight < 0 ? 0 : weight; // 重量非负校验
  }

  // 必须实现抽象方法1:实体商品售价计算逻辑
  override calculateSellingPrice(): number {
     return parseFloat((this._basePrice * (1 + this.profitRate)).toFixed(2));
  }

  // 必须实现抽象方法2:实体商品库存校验
  override checkStock(amount: number): boolean {
    if (amount <= 0) {
      console.log(`【${this.name}】校验失败:购买数量必须大于0`);
      return false;
    }
    if (amount > this.stock) {
      console.log(`【${this.name}】校验失败:库存不足,当前库存${this.stock}件,请求${amount}件`);
      return false;
    }
    console.log(`【${this.name}】库存校验通过,剩余库存${this.stock}件`);
    return true;
  }

  // 重写普通方法:扩展实体商品专属信息
  override printBaseInfo(): void {
    super.printBaseInfo(); // 复用父类通用打印逻辑
    console.log(`
      商品库存:${this.stock}件
      商品重量:${this.weight}kg
    `);
  }

  // 个性方法:基于库存校验的扣减逻辑
  reduceStock(amount: number): void {
    if (this.checkStock(amount)) {
      this.stock -= amount;
      console.log(`【${this.name}】库存扣减成功,剩余库存:${this.stock}件`);
    }
  }
}

2. 改造虚拟商品类(实现抽象方法)

// model/VirtualGoods.ets
import { AbstractGoods } from './AbstractGoods';

export class VirtualGoods extends AbstractGoods {
  public validDays: number; // 有效期(天)
  public isAutoRenew: boolean = false; // 自动续费(默认关闭)

  /**
   * 构造函数:优化参数顺序(必选参数 → 有默认值参数)
   * @param name 商品名称
   * @param basePrice 商品底价
   * @param validDays 有效期(默认:30天)
   * @param isAutoRenew 是否自动续费(默认:false)
   * @param category 商品分类(默认:虚拟服务)
   * @param profitRate 利润率(默认:0.15)
   */
  constructor(
    name: string,
    basePrice: number,
    validDays: number = 30, // 有默认值:常用可选参数在前
    isAutoRenew: boolean = false, // 有默认值:代替可选参数?,避免undefined
    category: string = "虚拟服务", // 有默认值
    profitRate: number = 0.15 // 有默认值:不常用可选参数在后
  ) {
    super(name, basePrice, category, profitRate); // 调用父类构造
    this.validDays = validDays < 1 ? 30 : validDays; // 有效期校验
    this.isAutoRenew = isAutoRenew;
  }

  // 重写普通方法:扩展打印逻辑(补充override,符合显式重写规范)
  override printBaseInfo(): void {
    super.printBaseInfo(); // 复用父类逻辑
    console.log(`
      有效期:${this.validDays}天
      自动续费:${this.isAutoRenew ? "开启" : "关闭"}
    `);
  }

  // 必须实现抽象方法1:虚拟商品售价计算逻辑
  override calculateSellingPrice(): number {
    const profitPart = this._basePrice * this.profitRate;
    const serviceFee = this._basePrice * 0.05; // 5%服务费(虚拟商品专属)
    const finalPrice = this._basePrice + profitPart + serviceFee;
    return Number(finalPrice.toFixed(2)); // 统一数值类型处理
  }

  // 必须实现抽象方法2:虚拟商品无实体库存,仅校验购买数量
  override checkStock(amount: number): boolean {
    if (amount <= 0) {
      console.log(`【${this.name}】校验失败:购买数量必须大于0`);
      return false;
    }
    console.log(`【${this.name}】虚拟商品无实体库存,库存校验通过`);
    return true;
  }

  // 个性方法:延长有效期
  extendValidity(days: number): void {
    if (days <= 0) {
      console.log("有效期延长失败:天数不合法");
      return;
    }
    this.validDays += days;
    console.log(`有效期延长${days}天,总计${this.validDays}天`);
  }
}

3. 新增秒杀商品类(扩展抽象类)

// model/SeckillGoods.ets(新增子类)
import { AbstractGoods } from './AbstractGoods';

export class SeckillGoods extends AbstractGoods {
  public seckillStock: number; // 个性属性:秒杀库存
  public discountRate: number; // 个性属性:折扣率(0.8=8折)
  public seckillStartTime: Date; // 个性属性:秒杀开始时间(Date类型)
  public seckillEndTime: Date; // 个性属性:秒杀结束时间(Date类型)

  /**
   * 构造函数:优化参数顺序,时间类型改为Date
   * @param name 商品名称
   * @param basePrice 商品底价
   * @param seckillStock 秒杀库存
   * @param discountRate 折扣率
   * @param seckillStartTime 秒杀开始时间
   * @param seckillEndTime 秒杀结束时间
   * @param category 商品分类(默认:秒杀商品)
   * @param profitRate 利润率(默认:0.1)
   */
  constructor(
    name: string,
    basePrice: number,
    seckillStock: number,
    discountRate: number,
    seckillStartTime: Date,
    seckillEndTime: Date,
    category: string = "秒杀商品", // 有默认值
    profitRate: number = 0.1 // 有默认值
  ) {
    super(name, basePrice, category, profitRate); // 分类默认值
    this.seckillStock = seckillStock < 0 ? 0 : seckillStock; // 秒杀库存非负
    // 折扣率约束:0.1~1(最低1折,最高原价)
    this.discountRate = discountRate < 0.1 || discountRate > 1 ? 0.8 : discountRate;
    this.seckillStartTime = seckillStartTime;
    this.seckillEndTime = seckillEndTime;
  }

  // 必须实现抽象方法1:秒杀商品售价计算逻辑
  override calculateSellingPrice(): number {
    const now = new Date();
    // 非秒杀时段按原价出售(Date对象直接比较,简化逻辑)
    if (now < this.seckillStartTime || now > this.seckillEndTime) {
      console.log(`【${this.name}】非秒杀时段(${this.seckillStartTime.toLocaleString()} - ${this.seckillEndTime.toLocaleString()}),按原价出售`);
      const normalPrice = this._basePrice * (1 + this.profitRate);
      return Number(normalPrice.toFixed(2));
    }
    // 秒杀时段按折扣价出售
    const seckillPrice = this._basePrice * this.discountRate;
    return Number(seckillPrice.toFixed(2));
  }

  // 必须实现抽象方法2:秒杀商品库存+时段双重校验
  override checkStock(amount: number): boolean {
    const now = new Date();
    // 时段校验(Date对象直接比较)
    if (now < this.seckillStartTime) {
      console.log(`【${this.name}】校验失败:秒杀活动尚未开始(开始时间:${this.seckillStartTime.toLocaleString()})`);
      return false;
    }
    if (now > this.seckillEndTime) {
      console.log(`【${this.name}】校验失败:秒杀活动已结束(结束时间:${this.seckillEndTime.toLocaleString()})`);
      return false;
    }
    // 数量校验
    if (amount <= 0) {
      console.log(`【${this.name}】校验失败:购买数量必须大于0`);
      return false;
    }
    // 库存校验
    if (amount > this.seckillStock) {
      console.log(`【${this.name}】校验失败:秒杀库存不足,当前库存${this.seckillStock}件,请求${amount}件`);
      return false;
    }
    console.log(`【${this.name}】秒杀库存校验通过,剩余库存${this.seckillStock}件`);
    return true;
  }

  // 重写普通方法:扩展秒杀商品专属信息
  override printBaseInfo(): void {
    super.printBaseInfo(); // 复用父类逻辑
    const startTime = this.seckillStartTime.toLocaleString();
    const endTime = this.seckillEndTime.toLocaleString();
    console.log(`
      秒杀库存:${this.seckillStock}件
      秒杀折扣:${(this.discountRate * 10).toFixed(1)}折
      秒杀时段:${startTime} - ${endTime}
    `);
  }

  // 个性方法:秒杀库存扣减
  // 下节接口章节将把“秒杀能力”抽离为独立接口(ISeckill),通过implements实现多维度能力扩展
  reduceSeckillStock(amount: number): void {
    if (this.checkStock(amount)) {
      this.seckillStock -= amount;
      console.log(`【${this.name}】秒杀库存扣减${amount}件,剩余:${this.seckillStock}件`);
    }
  }
}

四、抽象类+多态:实现商品统一管控

4.1 核心调用逻辑(规范+灵活)

通过抽象类类型数组统一管理所有商品子类实例,实现“规范接口+个性化实现”的统一调用。UI代码见附录,核心逻辑如下:


import { PhysicalGoods } from '../model/PhysicalGoods';
import { VirtualGoods } from '../model/VirtualGoods';
import { SeckillGoods } from '../model/SeckillGoods';
import { AbstractGoods } from '../model/AbstractGoods';
import { LengthMetrics } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  @State goodsList: AbstractGoods[] = [
    new PhysicalGoods("鸿蒙Mate70手机", 6999, 200, 0.2, "智能手机"),
    new VirtualGoods("鸿蒙VIP会员", 99, 365, true),
    new SeckillGoods("鸿蒙智能手表GT5(秒杀款)", 1299, 50, 0.7, new Date(), new Date(Date.now() + 3600 * 1000 * 2))];

  /**
   * 统一执行商品核心逻辑
   */
  private callSubclassMethods(): void {
    console.log("===== 鸿蒙电商商品核心逻辑执行 =====");

    this.goodsList.forEach((goods, index) => {
      console.log(`\n【商品${index + 1}:${goods.name}】`);
      goods.printBaseInfo(); // 复用/扩展父类打印逻辑
      console.log(`最终售价:${goods.calculateSellingPrice()}元`); // 执行子类售价计算

      // 库存校验(执行子类校验逻辑)
      const checkResult = goods.checkStock(2);
      console.log(`购买2件库存校验结果:${checkResult ? "✅ 通过" : "❌ 失败"}`);

      // 调用子类专属方法(instanceof 判断)
      if (goods instanceof PhysicalGoods) {
        goods.reduceStock(2);
      } else if (goods instanceof SeckillGoods) {
        goods.reduceSeckillStock(2);
      }
    });

    // 触发UI刷新:扩展运算符创建新数组,改变引用
    this.goodsList = [...this.goodsList];
  }

  /**
   * 扣减实体商品库存(直接断言+提示)
   */
  private handleReduceStock(goods: AbstractGoods, amount: number = 1): void {
    if (goods instanceof PhysicalGoods) {
      console.log(`\n===== 执行${goods.name}:扣减库存${amount}件 =====`);
      goods.reduceStock(amount);
      this.goodsList = [...this.goodsList]; // 触发UI刷新
    } else {
      console.log(`\n❌ ${goods.name}不是实体商品,无法扣减库存`);
    }
  }

  /**
   * 扣减秒杀商品库存(直接断言+提示)
   */
  private handleReduceSeckillStock(goods: AbstractGoods, amount: number = 1): void {
    if (goods instanceof SeckillGoods) {
      console.log(`\n===== 执行${goods.name}:扣减秒杀库存${amount}件 =====`);
      goods.reduceSeckillStock(amount);
      this.goodsList = [...this.goodsList]; // 触发UI刷新
    } else {
      console.log(`\n❌ ${goods.name}不是秒杀商品,无法扣减秒杀库存`);
    }
  }

  /**
   * 购买虚拟商品(直接断言+提示)
   */
  private handleBuyVirtualGoods(goods: AbstractGoods): void {
    if (goods instanceof VirtualGoods) {
      console.log(`\n===== 执行${goods.name}:购买操作 =====`);
      console.log(`${goods.name}购买成功!有效期:${goods.validDays}天 | 自动续费:${goods.isAutoRenew ? '开启' : '关闭'}`);
    } else {
      console.log(`\n❌ ${goods.name}不是虚拟商品,无法执行购买操作`);
    }
  }

  /**
   * 格式化日期为友好的字符串
   */
  private formatDate(date: Date): string {
    return date.toLocaleString('zh-CN', {
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit'
    });
  }

  build() {
    Column() {
      // 页面标题
      Text("抽象类+多态 商品管理实战")
        .fontSize(22)
        .fontWeight(FontWeight.Bold)
        .margin(20);

      // 全局操作按钮:执行所有商品的核心逻辑
      Button("执行所有商品核心操作")
        .width('90%')
        .height(40)
        .backgroundColor(Color.Blue)
        .fontColor(Color.White)
        .borderRadius(8)
        .margin({ bottom: 10 })
        .onClick(() => this.callSubclassMethods());

      // 商品列表滚动区域
      Scroll() {
        Column() {
          // 统一渲染所有商品(多态渲染)
          ForEach(this.goodsList, (goods: AbstractGoods) => {
            Column() {
              // 商品基础信息
              Text(goods.name)
                .fontSize(18)
                .fontWeight(FontWeight.Medium);
              Text(`分类:${goods.category}`)
                .fontSize(14)
                .margin(2)
                .fontColor('#666666');
              Text(`底价:¥${goods.basePrice} | 利润率:${(goods.profitRate * 100).toFixed(1)}%`)
                .fontSize(14)
                .margin(2)
                .fontColor('#666666');
              Text(`售价:¥${goods.calculateSellingPrice().toFixed(2)}`)
                .fontSize(16)
                .fontColor(Color.Red)
                .margin(5);

              // 渲染子类专属信息
              if (goods instanceof PhysicalGoods) {
                Text(`库存:${goods.stock}件 | 重量:${goods.weight.toFixed(2)}kg`)
                  .fontSize(12)
                  .margin(2)
                  .fontColor('#999999');
              } else if (goods instanceof VirtualGoods) {
                Text(`有效期:${goods.validDays}天 | 自动续费:${goods.isAutoRenew ? '开启' : '关闭'}`)
                  .fontSize(12)
                  .margin(2)
                  .fontColor('#999999');
              } else if (goods instanceof SeckillGoods) {
                Text(`秒杀折扣:${(goods.discountRate * 10).toFixed(1)}折 | 剩余库存:${goods.seckillStock}件`)
                  .fontSize(12)
                  .margin(2)
                  .fontColor('#999999');
                Text(`秒杀时间:${this.formatDate(goods.seckillStartTime)} - ${this.formatDate(goods.seckillEndTime)}`)
                  .fontSize(10)
                  .margin(2)
                  .fontColor('#ff6600');
              }

              Flex({ space:{main:LengthMetrics.px(10),cross:LengthMetrics.px(10)}, justifyContent: FlexAlign.Center, wrap: FlexWrap.Wrap }) {
                // 实体商品:扣减库存按钮
                if (goods instanceof PhysicalGoods) {
                  Button("扣减库存")
                    .fontSize(10)
                    .padding({ left: 12, right: 12, top: 5, bottom: 5 })
                    .backgroundColor(Color.Green)
                    .fontColor(Color.White)
                    .borderRadius(4)
                    .onClick(() => this.handleReduceStock(goods, 1));
                }

                // 秒杀商品:扣减秒杀库存按钮
                if (goods instanceof SeckillGoods) {
                  Button("扣减秒杀库存")
                    .fontSize(10)
                    .padding({ left: 12, right: 12, top: 5, bottom: 5 })
                    .backgroundColor(Color.Orange)
                    .fontColor(Color.White)
                    .borderRadius(4)
                    .onClick(() => this.handleReduceSeckillStock(goods, 1));
                }

                // 虚拟商品:购买VIP按钮
                if (goods instanceof VirtualGoods) {
                  Button("购买Vip")
                    .fontSize(10)
                    .padding({ left: 12, right: 12, top: 5, bottom: 5 })
                    .backgroundColor(Color.Pink)
                    .fontColor(Color.White)
                    .borderRadius(4)
                    .onClick(() => this.handleBuyVirtualGoods(goods));
                }
              }.margin({ top: 10 })

            }
            .width('90%')
            .padding(15)
            .backgroundColor(Color.White)
            .borderRadius(10)
            .shadow({ radius: 5, color: '#cccccc', offsetX: 2, offsetY: 2 })
            .margin(10);
          })
        }.width('100%')
      }.layoutWeight(1)
      .width('100%')
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f5f5f5')
    .padding(10);
  }
}

4.2 运行预览

抽象与多态

方法重新与抽象

五、抽象类与普通类的核心区别

特性 抽象类(abstract class) 普通类(class)
实例化 不可直接实例化,仅能被继承 可直接实例化
成员组成 抽象方法+普通方法/属性 仅普通方法/属性
子类约束 强制子类实现所有抽象方法 子类可选择是否重写父类方法
核心价值 定义规范、强制约束、统一接口 封装逻辑、实现复用、实例化调用
编译校验 子类未实现抽象方法直接报错 无强制校验
适用场景 大型工程的基础规范类(商品基类) 具体业务对象(工具类、单一实例)

六、常见问题解答

6.1 抽象类可以有构造函数吗?

可以。抽象类的构造函数用于初始化自身属性,供子类通过super()调用——虽然抽象类不能直接实例化,但构造函数是父子类属性传递的核心通道,是抽象类封装通用属性的必要手段。

6.2 抽象类可以继承普通类吗?

可以。抽象类可通过extends继承普通类,同时可在抽象类中新增抽象方法,强制子类实现更多规范(如:abstract class AbstractGoods extends BaseData {})。

6.3 抽象方法可以有访问修饰符吗?

可以。抽象方法可添加public/protected修饰符(默认public),但不能使用private——子类无法访问父类private方法,自然无法实现抽象方法,编译器会直接报错。

6.4 抽象类和接口的核心区别是什么?

  • 抽象类是“部分实现”:包含通用逻辑+规范约束(抽象方法),侧重“是不是”(如:实体商品“是”商品);
  • 接口是“完全规范”:仅定义方法签名/属性约束,无任何实现逻辑,侧重“有没有”(如:商品“有”退换能力、“有”秒杀能力);
  • 语法限制:一个类只能继承一个抽象类,但可实现多个接口(后续接口章节会详细讲解)。

6.5 有默认值的参数和可选参数(?)该如何排序?

遵循必选参数 → 有默认值的参数 → 可选参数(?)的语法规则,且优先使用有默认值的参数代替可选参数。原因:

  1. 可选参数(?)放在有默认值的参数前会编译报错;
  2. 有默认值的参数可避免undefined风险,语义更清晰(如isAutoRenew: boolean = falseisAutoRenew?: boolean更安全)。

七、课堂小结

  1. override关键字实现方法显式重写,提升代码可读性,编译器强制校验方法签名一致性,是鸿蒙工程编码的规范要求;
  2. 抽象类通过abstract关键字定义,不可直接实例化,可包含抽象方法(无方法体)和普通方法/属性;
  3. 子类继承抽象类时必须实现所有抽象方法,这是保证业务逻辑完整性的核心约束,杜绝“空实现”隐患;
  4. 抽象类+多态的组合,实现了“规范统一接口+子类个性化实现”,是鸿蒙电商商品管理的核心架构模式;
  5. 抽象类侧重“定义规范、强制约束”,普通类侧重“封装逻辑、实例化复用”,需根据业务场景合理选型;
  6. 参数声明遵循“必选 → 有默认值 → 可选(?)”顺序,优先用有默认值的参数代替可选参数,避免undefined风险。

八、代码仓库

工程名称:ClassObjectDemo_2
本节代码已同步至:https://gitee.com/juhetianxia321/harmony-os-code-base.git

九、下节预告

ArkTS仅支持单继承,无法满足商品“同时具备秒杀、退换、预售”等多维度能力——下一节抽象与面向接口编程将突破单继承限制:

  1. 接口的定义语法与核心规则(interface关键字、方法签名、属性约束);
  2. 掌握implements关键字,实现类对多接口的实现,为商品灵活叠加组合能力;
  3. 深度辨析“抽象类(是不是)”与“接口(有没有)”的核心差异,明确技术选型逻辑;
  4. 基于“抽象类+接口”重构商品架构,打造“基础规范+灵活能力”的高内聚设计,适配复杂电商业务场景。

十、鸿蒙开发者学习与认证指引

(一)、官方学习班级报名(免费)

  1. 班级链接HarmonyOS赋能资源丰富度建设(第四期)
  2. 学号填写规则:填写个人手机号码即可完成班级信息登记

(二)、HarmonyOS应用开发者认证考试(免费)

  1. 考试链接HarmonyOS开发者能力认证入口
  2. 认证等级及适配人群
    • 基础认证:适配软件工程师、移动应用开发人员,需掌握HarmonyOS基础概念、DevEco Studio基础使用、ArkTS及ArkUI基础开发等能力;
    • 高级认证:适配项目经理、工程架构师,需掌握系统核心技术理念、应用架构设计、关键技术开发及应用上架运维等能力;
    • 专家认证:适配研发经理、解决方案专家,需掌握分布式技术原理、端云一体化开发、跨端迁移及性能优化等高级能力。
  3. 认证权益:通过认证可获得电子版证书以及其他专属权益。
posted @ 2025-12-12 17:21  鸿蒙-散修  阅读(0)  评论(0)    收藏  举报