散修带你入门鸿蒙应用开发基础第十二节:继承基础与多态入门

第十二节:继承基础与多态入门

炼气十二重天

【学习目标】

  1. 理解继承的核心价值(代码复用+逻辑扩展),掌握ArkTS中extends实现类继承的标准语法;
  2. 吃透super关键字的双重用法(调用父类构造、调用父类方法),规避子类构造函数/方法的常见错误;
  3. 掌握protected修饰符的访问规则,理解其在父子类数据共享中的核心作用(对比public/private);
  4. 理解多态的本质(父类引用指向子类对象),实现鸿蒙电商场景下多类型商品的统一管理;
  5. 识别继承的使用边界,避免“过度继承”导致的代码耦合问题。

【学习重点】

  • extends继承语法:子类构造函数必须优先调用super(),且参数匹配父类构造;
  • super关键字:构造函数内初始化父类属性、方法内复用父类逻辑的核心方式;
  • protected修饰符:类内/子类可访问、类外不可访问,兼顾数据安全与复用;
  • 多态三要素:继承、方法重写、父类引用指向子类对象(通俗理解:“父类框子装子类对象”);
  • 鸿蒙实战:基于商品类的继承与多态,实现实体/虚拟商品的统一管理(衔接第十一节的profitRate属性)。

一、工程结构

复制第十一节的ClassObjectDemo工程代码,重命名为ClassObjectDemo_1(保留第十一节的核心逻辑,仅改造Goods类并新增子类),新增子类文件后核心目录如下:

ClassObjectDemo_1/
├── entry/                          # 应用主模块
│   ├── src/main/ets/
│   │   ├── pages/
│   │   │   └── Index.ets           # 测试页面(继承+多态调用)
│   │   └── model/
│   │   │   ├── Goods.ets           # 父类:通用商品类(复用核心逻辑,含profitRate属性)
│   │   │   ├── PhysicalGoods.ets   # 子类:实体商品(扩展库存/重量)
│   │   │   └── VirtualGoods.ets    # 子类:虚拟商品(扩展有效期/自动续费)
│   ├── resources/                  # 资源目录
│   └── module.json5                # 模块配置
└── hvigorfile.ts                   # 构建脚本

说明:将第十一节的Goods.ets复制到新工程的model目录,仅修改_basePrice的修饰符为protected,保留profitRate属性,以支持子类访问和前后衔接。

二、继承基础:代码复用与逻辑扩展

2.1 继承的核心价值

第十一节实现的通用商品类Goods,可支撑基础商品管理,但实际业务中商品分为“实体商品”(手机、电脑)和“虚拟商品”(会员、课程):

  • 共性:名称、底价、利润利率,及售价计算、信息打印等基础逻辑;
  • 个性:实体商品需管理库存/重量,虚拟商品需管理有效期/自动续费,且不同商品利润率可自定义。

继承通过extends关键字将共性逻辑封装在父类,子类仅需实现个性功能,彻底解决多商品类型的逻辑冗余问题,实现“一次定义、多类复用”。

无继承的实现局限(简化示例)

// 实体商品类(需单独实现共性逻辑)
export class PhysicalGoods {
  public name: string;
  private _basePrice: number;
  public profitRate: number = 0.1;
  public stock: number;

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

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

// 虚拟商品类(需重复实现相同的共性逻辑)
export class VirtualGoods {
  public name: string;
  private _basePrice: number;
  public profitRate: number = 0.1;
  public validDays: number;

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

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

核心问题:共性逻辑(如售价计算)需在每个商品类中重复编写,修改时需同步调整所有类,维护成本高。

2.2 继承核心语法规则

  1. 继承语法:class 子类名 extends 父类名 { ... }
  2. super关键字:子类访问父类的唯一入口:
    • 构造函数内:super(父类参数),初始化父类属性,必须是构造函数第一行代码;
    • 方法内:super.父类方法(),复用父类逻辑后扩展子类功能;
  3. 成员访问:子类可继承父类public/protected成员,private成员仅父类内可访问;
  4. 方法重写:子类可重写父类方法(方法名、参数、返回值一致),实现个性逻辑;
  5. 属性复用:子类可直接使用或修改父类public属性(如profitRate)。

2.3 实战:商品类的继承实现

步骤1:改造父类(开放子类访问)

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

  constructor(name: string, basePrice: number, category?: string) {
    this.name = name;
    this._basePrice = basePrice > 0 ? basePrice : 0; // 底价非负校验
    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;
  }

  // 共性方法:计算售价
  calculateSellingPrice(): number {
    const sellingPrice = this._basePrice * (1 + this.profitRate);
    return parseFloat(sellingPrice.toFixed(2));
  }

  // 共性方法:打印基础信息
  printBaseInfo(): void {
    console.log(`
      商品名称:${this.name}
      商品分类:${this.category}
      商品底价:${this._basePrice}元
      利润利率:${this.profitRate * 100}%
    `);
  }
}

步骤2:实体商品子类(扩展个性属性)

// model/PhysicalGoods.ets(子类:实体商品)
import { Goods } from './Goods';

export class PhysicalGoods extends Goods {
  public stock: number; // 库存(件)
  public weight: number; // 重量(kg)

  constructor(
    name: string,
    basePrice: number,
    stock: number,
    weight: number,
    category?: string
  ) {
    super(name, basePrice, category); // 调用父类构造(必须在第一行)
    this.stock = stock < 0 ? 0 : stock; // 库存非负校验
    this.weight = weight < 0 ? 0 : weight; // 重量非负校验
    // 可选:自定义利润率(如8%),默认继承父类10%
    // this.profitRate = 0.08;
  }

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

  // 个性方法:扣减库存(父类没有的,子类独有的)
  reduceStock(amount: number): void {
    if (amount <= 0 || amount > this.stock) {
      console.log("库存扣减失败:数量不合法");
      return;
    }
    this.stock -= amount;
    console.log(`库存扣减${amount}件,剩余:${this.stock}件`);
  }
}

步骤3:虚拟商品子类

// model/VirtualGoods.ets(子类:虚拟商品)
import { Goods } from './Goods';

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

  constructor(
    name: string,
    basePrice: number,
    validDays: number = 30, // 默认有效期30天
    isAutoRenew?: boolean,
    category?: string
  ) {
    super(name, basePrice, category); // 调用父类构造
    this.validDays = validDays < 1 ? 30 : validDays; // 有效期校验
    if (isAutoRenew !== undefined) {
      this.isAutoRenew = isAutoRenew;
    }
    this.profitRate = 0.15; // 虚拟商品自定义利润率(15%)
  }

  // 重写父类方法:扩展打印逻辑
  printBaseInfo(): void {
    super.printBaseInfo(); // 复用父类逻辑
    console.log(`
      有效期:${this.validDays}天
      自动续费:${this.isAutoRenew ? "开启" : "关闭"}
    `);
  }

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

三、super关键字:父子类交互的核心

super是子类访问父类的唯一合法入口,具体用法如下:

使用场景 语法格式 核心作用
子类构造函数中 super(父类构造参数); 初始化父类属性,必须是构造函数第一行代码
子类方法中 super.父类方法名(参数); 复用父类逻辑,扩展子类功能

常见错误示例(避坑指南)

// 错误1:构造函数中未优先调用super()
export class BadChild1 extends Goods {
  public prop: number;
  constructor(name: string, basePrice: number, prop: number) {
    this.prop = prop; // ❌ 错误:需先调用super()
    super(name, basePrice);
  }
}

// 错误2:方法中用this调用父类方法(导致递归)
export class BadChild2 extends Goods {
  printBaseInfo(): void {
    this.printBaseInfo(); // ❌ 错误:应使用super.printBaseInfo()
  }
}

// 错误3:子类直接修改父类protected属性(绕过校验)
export class BadChild3 extends Goods {
  constructor(name: string, basePrice: number) {
    super(name, basePrice);
    this._basePrice = -100; // ❌ 不推荐:破坏数据安全性
  }
}

四、protected修饰符:数据共享与安全的平衡

4.1 访问范围对比

修饰符 类内 子类 类外 商品场景应用
public 名称、分类、利润利率(profitRate
private 支付密码等纯私有数据
protected 商品底价(_basePrice

4.2 核心价值

  • 复用性:子类可访问_basePrice,结合profitRate实现个性化售价计算;
  • 安全性:类外无法直接操作_basePrice,需通过父类get/set方法,保证数据合规;
  • 灵活性:profitRate作为public属性,子类和类外可直接调整,适配不同商品定价需求。

五、多态入门:多类型商品的统一管理

5.1 多态的本质

多态是“父类引用指向子类对象”,调用方法时自动执行子类重写逻辑,无需区分商品类型,实现“统一接口、多态实现”。

5.2 多态三要素

  1. 继承:子类继承父类(PhysicalGoods/VirtualGoods 通过extends关键词→ Goods);
  2. 方法重写:子类重写父类通用方法(如printBaseInfo);
  3. 父类引用:用Goods类型存储子类实例(const goods: Goods = new PhysicalGoods(...))。

5.3 鸿蒙实战:商品列表统一管理

// pages/Index.ets(多态实战)

import { Goods } from '../model/Goods';
import { PhysicalGoods } from '../model/PhysicalGoods';
import { VirtualGoods } from '../model/VirtualGoods';

@Entry
@Component
struct Index {
  @State goodsList: Goods[] = []; // 父类数组:统一存储所有商品

  aboutToAppear(): void {
    // 父类引用指向子类实例(多态核心)
    const phone: Goods = new PhysicalGoods("鸿蒙Mate70", 6999, 200, 0.2, "智能手机");
    const computer: Goods = new PhysicalGoods("鸿蒙笔记本", 8999, 100, 1.5, "电脑");
    const vip: Goods = new VirtualGoods("鸿蒙VIP", 99, 365, true, "虚拟服务");
    const manual: Goods = new Goods("鸿蒙开发手册", 59);

    // 自定义利润率
    computer.profitRate = 0.09; // 电脑利润率9%
    manual.profitRate = 0.12; // 手册利润率12%

    this.goodsList = [phone, computer, vip, manual];
    this.printAllGoods(); // 统一调用,自动适配子类逻辑
  }

  // 统一打印所有商品信息
  private printAllGoods(): void {
    console.log("===== 鸿蒙商品列表 =====");
    this.goodsList.forEach((goods, index) => {
      console.log(`\n【商品${index + 1}】`);
      goods.printBaseInfo(); // 自动执行子类重写逻辑
      console.log(`商品售价:${goods.calculateSellingPrice()}元`);
    });
  }


  /**
   * 调用子类专属方法:根据商品类型执行实体商品扣库存、虚拟商品延长期限的操作
   * 【新手专属说明】:
   * 1. 此处使用instanceof是为了演示**类型判断和执行子类个性方法**的核心功能(基础阶段重点);
   * 2. 真实开发注意事项:
   *    - 性能:若商品列表数据量极大,频繁遍历+instanceof判断需考虑性能优化(如按需处理);
   *    - UI刷新:此处用数组拷贝触发刷新是基础阶段的临时写法,真实开发中会结合@State/@Link/@Observed等装饰器做更优雅的状态管理;
   * 3. 数组拷贝的原因:@State装饰器监听的是数组**引用变化**,仅修改数组内元素不会触发UI刷新,需重新赋值新数组;
   * 4. 这部分UI刷新的逻辑暂时理解不了没关系,后续学习状态管理后会替换为更优写法。
   */
  private callSubclassMethods(): void {

    this.goodsList.forEach(goods => {
      if (goods instanceof PhysicalGoods) {
        // 无需使用 as 断言 ArkTS强类型静态检查,会提前检查是否存在 reduceStock方法,不存在编译报错。
        // (goods as PhysicalGoods).reduceStock(5);
        goods.reduceStock(5); // 实体商品扣库存
      }
      if (goods instanceof VirtualGoods) {
        goods.extendValidity(30); // 虚拟商品延有效期
      }
    });
    // 【UI刷新逻辑】:通过扩展运算符创建新数组,改变引用以触发@State的UI刷新
    const tempGoodsList = this.goodsList; // 临时保存修改后的数组
    this.goodsList = [...tempGoodsList]; // 重新赋值新数组,触发UI更新
  }

  build() {
    Column() {
      Text("ArkTS继承与多态实战")
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin(20);

      // 遍历渲染商品
      ForEach(this.goodsList, (goods: Goods) => {
        Column() {
          Text(`${goods.category}(${goods instanceof PhysicalGoods ? "实体" : goods instanceof VirtualGoods ? "虚拟" : "通用"}商品)`)
            .fontSize(18)
            .fontWeight(FontWeight.Medium)
            .margin({bottom:10});
          Text(`名称:${goods.name} | 底价:${goods.basePrice}元 | 利率:${goods.profitRate * 100}% | 售价:${goods.calculateSellingPrice()}元`)
            .fontSize(16);
          // 差异化信息渲染

          if (goods instanceof PhysicalGoods) {
            Text(`库存:${goods.stock}件 | 重量:${goods.weight}kg`)
              .fontSize(14)
              .fontColor("#666");
          } else if (goods instanceof VirtualGoods) {
            Text(`有效期:${goods.validDays}天 | 自动续费:${goods.isAutoRenew ? "开启" : "关闭"}`)
              .fontSize(14)
              .fontColor("#666");
          }
        }
        .width('100%')
        .padding(10)
        .backgroundColor('#f5f5f5')
        .borderRadius(8)
        .margin({bottom:10});
      });

      Button("执行子类专属操作")
        .width('100%')
        .height(40)
        .onClick(() => {
          this.callSubclassMethods()
        });

    }.width('100%').padding(20);
  }
}

5.4 预览运行

继承与多态

5.4 多态的核心优势

  1. 简化代码:无需大量if-else判断商品类型,统一通过父类接口调用;
  2. 易扩展:新增“秒杀商品”子类时,仅需继承Goods,无需修改列表管理代码;
  3. 符合开闭原则:扩展新功能不修改原有代码,降低维护成本。

六、常见问题解答

6.1 子类必须重写父类方法吗?

不必。仅需在逻辑与父类不同时重写,若逻辑一致(如实体商品的售价计算),可直接复用父类方法。

6.2 父类引用如何调用子类专属方法?

需通过instanceof 类型判断 (只能判断类):

const goods: Goods = new PhysicalGoods("智能手表", 1299, 300, 0.05);
if (goods instanceof PhysicalGoods) {
    goods.reduceStock(10); // 合法调用
}

6.3 子类可修改父类public属性(如profitRate)吗?

可以。public属性对类内、子类、类外均可见,子类可直接修改(如虚拟商品设置this.profitRate = 0.15)。若需管控修改,可将profitRate改为private并提供get/set方法。

6.4 ArkTS支持多继承吗?

不支持。仅支持单继承,多维度能力扩展可通过后续“接口”章节实现。

6.5 如何避免继承导致的代码耦合?

  1. 单一职责:父类仅封装通用逻辑,不包含子类专属功能;
  2. 优先组合:如促销逻辑通过引用Promotion类实现,而非继承;
  3. 少暴露protected属性:优先提供方法(如get basePrice()),降低子类对父类实现的依赖。

七、课堂小结

  1. 继承通过extends实现“共性复用、个性扩展”,子类构造函数必须优先调用super()
  2. super是父子类交互的唯一入口:构造函数内初始化父类属性,方法内复用父类逻辑;
  3. protected修饰符平衡“共享性”与“安全性”,public属性(如profitRate)适配灵活配置场景;
  4. 多态的核心是“父类引用指向子类对象”,实现多类型商品的统一管理,提升代码扩展性;
  5. 继承解决“复用”,多态解决“统一调用”,二者结合是鸿蒙电商商品管理的核心设计思路;
  6. 避免过度继承,优先组合而非继承,降低代码耦合度。

八、代码仓库

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

九、下节预告

  1. 下一节方法重写与抽象类应用将深入学习:
    • override显式重写:规范子类方法重写,提升代码可读性;
    • 抽象类/抽象方法:强制子类实现核心逻辑,避免“空实现”;
    • 多态进阶:基于抽象类实现严格的商品规范(如强制子类设置profitRate);
  2. 抽象类是鸿蒙大型工程中保证代码规范性的核心手段。

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

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

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

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

  1. 考试链接HarmonyOS开发者能力认证入口

  2. 认证等级及适配人群

    • 基础认证:适配软件工程师、移动应用开发人员,需掌握HarmonyOS基础概念、DevEco Studio基础使用、ArkTS及ArkUI基础开发等能力;
    • 高级认证:适配项目经理、工程架构师,需掌握系统核心技术理念、应用架构设计、关键技术开发及应用上架运维等能力;
    • 专家认证:适配研发经理、解决方案专家,需掌握分布式技术原理、端云一体化开发、跨端迁移及性能优化等高级能力。
  3. 认证权益:通过认证可获得电子版证书以及其他专属权益。

posted @ 2025-12-12 17:19  鸿蒙-散修  阅读(0)  评论(0)    收藏  举报