零基础鸿蒙应用开发第二十二节:类的继承与多态入门

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

【学习目标】

  1. 理解继承的核心意义,掌握ArkTS中extends关键字的使用规则,区分“单继承”特性在鸿蒙开发中的适配场景;
  2. 掌握super关键字的核心作用(调用父类构造函数、调用父类方法),规避继承中的常见语法错误;
  3. 吃透protected访问修饰符的访问范围,掌握其与public/private的差异及在层级类中的应用;
  4. 熟悉方法重写的概念,理解隐式重写与显式重写的区别,掌握override关键字的规范用法,实现子类对父类方法的自定义扩展;
  5. 理解多态的核心概念(父类引用指向子类对象),掌握鸿蒙电商场景下父类引用管理子类对象的基础用法;
  6. 发现普通父类的两大局限性:可直接实例化无业务意义的对象、无法强制子类实现差异化核心业务逻辑,为后续学习抽象类做好铺垫,实现鸿蒙电商商户端商品分类的统一管理。

【学习重点】

  • ArkTS“单继承”规则与鸿蒙开发的适配性,避免多重继承的语法错误;
  • super关键字在子类构造函数中的强制调用规则,参数需与父类构造函数完全对齐;
  • protected修饰符的核心应用:子类访问父类的成本价(底价)字段,类外不可访问;
  • 方法重写的完整逻辑:先掌握隐式重写(无需override),再理解override的规范价值,明确重写的核心规则(签名一致、访问修饰符约束);
  • 多态的基础实现:父类引用指向子类对象,简化商户端商品列表的统一管理;
  • 普通父类的局限性:①Goods类可直接实例化,无法约束商品必须属于具体子类(如数码、图书);②父类默认方法实现无法强制子类差异化实现核心业务逻辑(如折后价计算);
  • 与上一节Goods类的衔接:新增costPrice属性及对应逻辑,保留原有setPrice/getPriceprintInfo等核心方法,仅重构折后价计算逻辑。

一、工程结构

本节示例代码基于上一节工程,复制一份重命名为ClassObjectDemo_1工程,核心结构如下:

ClassObjectDemo_1/
├── entry/                          # 应用主模块
│   ├── src/
│   │   ├── main/
│   │   │   ├── ets/                # ArkTS代码根目录
│   │   │   │   ├── pages/          # 页面代码目录
│   │   │   │   │   └── Index.ets   # 测试页面
│   │   │   │   └── model/          # 业务模型类目录
│   │   │   │       ├── Goods.ets   # 父类:商品基类
│   │   │   │       ├── DigitalGoods.ets  # 新增子类:数码商品类
│   │   │   │       └── BookGoods.ets     #  新增子类:图书商品类
│   │   │   ├── resources/          # 资源目录
│   │   │   └── module.json5        # 模块配置文件
│   │   └── oh-package.json5        # 工程依赖配置
└── hvigorfile.ts                   # 构建脚本

二、继承基础解析

2.1 为什么需要继承?

上一节实现了通用的Goods类,封装了商品的名称、价格、库存、折扣等核心属性和通用方法,但电商场景中存在“数码商品”“图书商品”等细分类型:

  • 共性:名称、价格、库存、折扣、打印信息等;
  • 个性:数码商品有品牌/保修年限,图书商品有作者/出版日期,且需计算单件利润。

若为每种商品单独写类,会重复编写价格校验、折扣计算、库存管理等通用逻辑;通过继承,子类可复用父类Goods的所有通用属性/方法,仅需实现专属逻辑,大幅减少代码冗余,同时保证逻辑统一。

2.2 继承的标准语法

ArkTS遵循单继承规则,子类通过extends关键字继承父类,核心语法如下:

// 父类:通用属性与方法
export class ParentClass {
  public publicAttr: string;
  protected protectedAttr: number;
  private privateAttr: boolean;

  constructor(attr1: string, attr2: number) {
    this.publicAttr = attr1;
    this.protectedAttr = attr2;
    this.privateAttr = true;
  }

  public publicMethod(): void {
    console.log("父类通用方法");
  }
}

// 子类:继承父类并扩展专属逻辑
export class ChildClass extends ParentClass {
  private childAttr: string;

  constructor(attr1: string, attr2: number, childAttr: string) {
    super(attr1, attr2);
    this.childAttr = childAttr;
  }

  // 隐式重写
  public publicMethod(): void {
    super.publicMethod();
    console.log("子类扩展方法,专属属性:", this.childAttr);
  }
}

// 调用测试
const childObj = new ChildClass("测试属性1", 100, "子类专属属性");
childObj.publicMethod();

打印输出

父类通用方法
子类扩展方法,专属属性: 子类专属属性

三、实战:商品类的继承实现

3.1 父类Goods


export class Goods {
  public name: string;
  category: string = "商品分类";

  protected costPrice: number;

  private _price: number;
  private _stock: number;
  private _discount: number = 1;

  constructor(
    name: string,
    price: number,
    stock: number,
    costPrice: number,
    discount?: number,
    category?: string
  ) {
    this.name = name;
    this._price = Math.max(price, 0);
    this._stock = Math.max(stock, 0);
    this.costPrice = Math.max(costPrice, 0);

    if (discount && discount > 0 && discount <= 1) {
      this.discount = discount;
    }

    this.category = category?.trim() || this.category;
  }

  calculateFinalPrice(): number {
    return parseFloat((this._price * this.discount).toFixed(2));
  }

  printInfo(): void {
    const finalPrice = this.calculateFinalPrice();
    const showDiscount = this.discount * 10;

    console.log(`
      商品名称:${this.name}
      商品分类:${this.category}
      商品原价:${this.price}元
      商品折扣:${showDiscount}折
      折后售价:${finalPrice}元
      商品库存:${this.stock}件
    `);
  }

  setPrice(newPrice: number) {
    if (newPrice < 0) {
      console.warn(`【${this.name}】售价不能为负,修改失败`);
      return;
    }
    this._price = newPrice;
    console.log(`【${this.name}】售价修改为:${newPrice}元`);
  }

  getPrice() {
    return this._price;
  }

  set price(newPrice: number) {
    if (newPrice < 0) {
      console.warn(`【${this.name}】售价不能为负,修改失败`);
      return;
    }
    this._price = newPrice;
    console.log(`【${this.name}】售价修改为:${newPrice}元`);
  }

  get price() {
    return this._price;
  }

  set discount(newDiscount: number) {
    if (newDiscount > 0 && newDiscount <= 1) {
      this._discount = newDiscount;
      console.log(`【${this.name}】折扣修改为:${newDiscount * 10}折`);
    } else {
      console.warn(`【${this.name}】折扣无效,需0~1之间,修改失败`);
    }
  }

  get discount() {
    return this._discount;
  }

  set stock(newStock: number) {
    if (newStock < 0) {
      console.warn(`【${this.name}】库存不能为负,修改失败`);
      return;
    }
    this._stock = newStock;
    console.log(`【${this.name}】库存修改为:${newStock}件`);
  }

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

3.2 子类DigitalGoods(数码商品类)

// model/DigitalGoods.ets
import { Goods } from './Goods';

export class DigitalGoods extends Goods {
  private brand: string;
  private warranty: number;

  constructor(
    name: string,
    price: number,
    stock: number,
    costPrice: number,
    brand: string,
    warranty: number,
    discount?: number,
    category?: string
  ) {
    super(name, price, stock, costPrice, discount, category || "数码产品");
    this.brand = brand?.trim() || "未知品牌";
    this.warranty = Math.max(warranty, 1);
  }

  calculateProfit(): number {
    const finalPrice = this.calculateFinalPrice();
    return parseFloat((finalPrice - this.costPrice).toFixed(2));
  }

  public printInfo(): void {
    super.printInfo();
    console.log(`
      商品品牌:${this.brand}
      保修年限:${this.warranty}年
      单件利润:${this.calculateProfit()}元
    `);
  }
}

调用测试

import { DigitalGoods } from '../model/DigitalGoods';
import { Goods } from '../model/Goods';

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

  aboutToAppear(): void {
    // 测试:实例化数码商品
    const phone = new DigitalGoods("鸿蒙Mate70", 5999, 100, 4000, "华为", 2, 0.8);
    phone.printInfo();
  }
  build() {
    Text("鸿蒙商品")
      .fontSize(20)
      .fontWeight(FontWeight.Bold)
      .margin(20);
  }
}

打印输出

      商品名称:鸿蒙Mate70
      商品分类:数码产品
      商品原价:5999元
      商品折扣:0.8折
      折后售价:4799.2元
      商品库存:100件

      商品品牌:华为
      保修年限:2年
      单件利润:799.2元

3.3 子类BookGoods(图书商品类)

// model/BookGoods.ets
import { Goods } from './Goods';

export class BookGoods extends Goods {
  private author: string;
  private publishDate: string;

  constructor(
    name: string,
    price: number,
    stock: number,
    costPrice: number,
    author: string,
    publishDate: string,
    discount?: number,
    category?: string
  ) {
    super(name, price, stock, costPrice, discount, category || "图书");
    this.author = author?.trim() || "未知作者";
    this.publishDate = publishDate?.trim() || "未知日期";
  }

  calculateBookProfit(): number {
    const finalPrice = this.calculateFinalPrice();
    return parseFloat((finalPrice - this.costPrice).toFixed(2));
  }

  public printInfo(): void {
    super.printInfo();
    console.log(`
      图书作者:${this.author}
      出版日期:${this.publishDate}
      单件利润:${this.calculateBookProfit()}元
    `);
  }

  getAuthorInfo(): string {
    return `《${this.name}》的作者是${this.author}`;
  }
}

调用测试

import { BookGoods } from '../model/BookGoods';

// 测试:实例化图书商品
const book = new BookGoods("鸿蒙开发实战", 69, 500, 30, "鸿蒙官方", "2025-01", 0.7);
book.printInfo();
console.log(book.getAuthorInfo());

打印输出

      商品名称:鸿蒙开发实战
      商品分类:图书
      商品原价:69元
      商品折扣:0.7折
      折后售价:48.3元
      商品库存:500件

      图书作者:鸿蒙官方
      出版日期:2025-01
      单件利润:18.3元

《鸿蒙开发实战》的作者是鸿蒙官方

四、核心关键字与访问修饰符解析

4.1 super关键字的核心作用

super是子类与父类交互的唯一入口,核心用法如下:

作用场景 语法格式 核心说明
调用父类构造函数 super(参数1, 参数2, ...) 1. 必须放在子类构造函数第一行;
2. 参数需与父类构造函数完全对齐;
3. 未调用会直接编译报错。
调用父类普通方法 super.方法名(参数) 在子类重写的方法中,复用父类原有逻辑。

错误示例

// 错误1:未调用super()直接初始化子类属性
class ErrorDigital extends Goods {
  constructor() {
    this.brand = "华为";
  }
}

// 错误2:super参数与父类构造函数不匹配
class ErrorBook extends Goods {
  constructor(name: string, price: number, stock: number) {
    super(name, price, stock);
  }
}

4.2 protected访问修饰符

修饰符 父类内访问 子类内访问 类外访问 应用场景
public name、category、printInfo等
protected costPrice
private _price、_stock、_discount等

实战验证

const phone = new DigitalGoods("鸿蒙Mate70", 5999, 100, 4000, "华为", 2);
console.log("利润:",phone.calculateProfit());
console.log(phone.name);
// console.log(phone.costPrice);
// console.log(phone._price);

4.3 重写核心规则

  1. 方法签名必须与父类一致;
  2. 访问修饰符权限不能缩小;
  3. 子类重写时可通过super调用父类原逻辑;
  4. 父类私有方法无法被重写。

4.4 显式重写

添加override关键字,提升可读性与安全性,方便其他开发人员看见override关键字立刻知道这是重写方法。

public override printInfo(): void {
  super.printInfo();
  console.log(`
    图书作者:${this.author}
    出版日期:${this.publishDate}
    单件利润:${this.calculateBookProfit()}元
  `);
}

隐式重写的缺点,其他开发人员想要确定这是重写方法需查看父类。辨识度较低,修改父类方法后隐式重写可能会变成子类独有方法。

错误示例

// 错误1:方法签名不一致
public printInfo(format: boolean): void { 
  super.printInfo();
}

// 错误2:访问修饰符权限缩小
protected printInfo(): void {
  super.printInfo();
}

4.4 继承的核心规则

  1. 单继承规则:ArkTS不支持多重继承,如class A extends B, C会直接编译报错;
  2. 属性继承规则:子类仅继承父类的publicprotected属性/方法;
  3. 扩展性规则:新增商品类型,仅需继承Goods类扩展专属逻辑;
  4. 普通父类的潜在问题
  • 可直接实例化无业务意义的纯商品对象;
  • 父类的calculateFinalPrice方法为固定实现,无法强制子类差异化实现;
  • 隐式重写无编译器校验,易出现“伪重写”导致多态逻辑失效。

五、多态入门

5.1 多态的本质

多态的本质是 “接口统一,实现各异”,父类定义了统一的方法接口,子类通过重写提供不同的实现,通过父类引用调用时,会根据实际指向的子类对象执行对应的实现。

5.2 多态三要素

  1. 继承:子类继承父类;
  2. 方法重写:子类重写父类通用方法;
  3. 父类引用:用Goods类型存储子类实例。

5.3 多态的工程化实现

// pages/Index.ets
import { Goods } from '../model/Goods';
import { DigitalGoods } from '../model/DigitalGoods';
import { BookGoods } from '../model/BookGoods';

@Entry
@Component
struct Index {

  aboutToAppear(): void {

    const digital: Goods = new DigitalGoods("鸿蒙Mate70手机", 5999, 100, 4000, "华为", 2, 0.8, "数码产品");
    const book: Goods = new BookGoods("鸿蒙开发实战", 69, 500, 30, "鸿蒙官方", "2025-01", 0.7, "图书");
    const invalid: Goods = new Goods("鸿蒙智能手表", 1299, 80, 800, 0.9, "穿戴设备");

    const goodsList: Goods[] = [digital, book, invalid];

    goodsList.forEach((goods, index) => {
      console.log(`\n===== 商品${index + 1}信息 =====`);
      goods.printInfo();
    });

    this.batchUpdateStock(goodsList, 50);
    this.calculateTotalStock(goodsList);

    if (book instanceof BookGoods) {
      console.log("\n" + book.getAuthorInfo());
    }
  }

  private batchUpdateStock(goodsList: Goods[], num: number): void {
    console.log(`\n=== 批量增加${num}件商品库存 ===`);
    goodsList.forEach(goods => {
      goods.stock = goods.stock + num;
    });
  }

  private calculateTotalStock(goodsList: Goods[]): void {
    const total = goodsList.reduce((sum, goods) => sum + goods.stock, 0);
    console.log(`\n=== 所有商品总库存:${total} ===`);
  }

  build() {
    Column() {
      Text("鸿蒙电商商品管理-继承与多态")
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin(20);
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center);
  }
}

打印输出

【鸿蒙Mate70手机】折扣修改为:8折
【鸿蒙开发实战】折扣修改为:7折
【鸿蒙智能手表】折扣修改为:9折

===== 商品1信息 =====
商品名称:鸿蒙Mate70手机
商品分类:数码产品
商品原价:5999元
商品折扣:8折
折后售价:4799.2元
商品库存:100件

商品品牌:华为
保修年限:2年
单件利润:799.2元

===== 商品2信息 =====
商品名称:鸿蒙开发实战
商品分类:图书
商品原价:69元
商品折扣:7折
折后售价:48.3元
商品库存:500件

图书作者:鸿蒙官方
出版日期:2025-01
单件利润:18.3元

===== 商品3信息 =====
商品名称:鸿蒙智能手表
商品分类:穿戴设备
商品原价:1299元
商品折扣:9折
折后售价:1169.1元
商品库存:80件

=== 批量增加50件商品库存 ===
【鸿蒙Mate70手机】库存修改为:150件
【鸿蒙开发实战】库存修改为:550件
【鸿蒙智能手表】库存修改为:130件

=== 所有商品总库存:830 ===
《鸿蒙开发实战》的作者是鸿蒙官方

5.4 多态的核心优势

  1. 代码简洁:无需用if-else判断商品类型,统一通过父类接口操作;
  2. 扩展性极强:新增商品类型,无需修改原有批量操作、统计逻辑;
  3. 显式重写提升稳定性:使用override关键字可避免“伪重写”;
  4. 暴露普通父类的设计缺陷
    • 可实例化无业务意义的纯商品对象;
    • 父类默认方法实现无法强制子类差异化实现核心业务逻辑。

六、常见问题解答

6.1 为什么不建议直接实例化Goods类?

  1. 从业务逻辑看,商品必须是具体类型,不存在“无类型的纯商品”;
  2. 从技术角度看,直接实例化会导致:
  • 代码约束性差,容易出现不符合业务规则的对象;
  • 纯商品对象的calculateFinalPrice方法使用父类默认实现,无法适配后续差异化的定价逻辑。

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

父类引用需通过类型断言判断类型,符合条件直接调用子类专属方法:

if (book instanceof BookGoods) { 
  book.getAuthorInfo();
}

6.3 隐式重写和显式重写的区别是什么?

对比维度 隐式重写 显式重写
写法难度 简单 稍复杂
可读性
编译器校验
维护成本
工程化推荐度 不推荐 强烈推荐

6.4 为什么父类的calculateFinalPrice方法不适合所有商品类型?

父类的calculateFinalPrice仅实现了“售价×折扣系数”的固定逻辑,但不同商品存在差异化的定价需求,如叠加满减活动、结合会员折扣,该方法无法满足这些多样化的业务需求。

七、内容总结

  1. 继承的核心是代码复用与逻辑统一,子类通过extends继承父类,需先调用super()初始化父类必传属性,protected修饰符实现了父子类之间的数据共享;
  2. 方法重写有两种实现方式:隐式重写和显式重写,核心规则是签名一致、访问修饰符不能更严格,可通过super复用父类逻辑;
  3. 多态通过父类引用指向子类对象实现对不同实体商品的统一管理,显式重写能提升多态逻辑的稳定性,新增商品类型仅需扩展子类,扩展性极强;
  4. 普通父类存在两大核心局限性:可直接实例化无业务意义的对象、默认方法实现无法强制子类差异化实现核心业务逻辑,这两个问题将在下一节通过抽象类与抽象方法解决。

八、代码仓库

九、下节预告

本节我们已经掌握了继承的核心用法、super关键字与protected修饰符的应用,以及方法重写和多态的基础实现逻辑,也明确了普通父类的两大局限性。下一节我们将针对这些问题展开进阶学习,深入探索抽象类的核心知识与实战应用,核心内容包括:

  1. 抽象类的定义与实战:将Goods基类改造为AbstractGoods抽象类,从语法层面禁止直接实例化无业务意义的对象;
  2. 抽象方法的声明与实现:将calculateFinalPrice改为抽象方法,强制子类实现差异化的定价逻辑,解决业务约束问题;
  3. 抽象类+多态的进阶应用:新增商品子类虚拟类商品,实现抽象类引用管理所有商品对象,并结合鸿蒙ArkUI完成商品列表的UI渲染;
  4. 抽象类与普通类的核心差异:从语法、业务、工程化三个维度对比,明确抽象类在鸿蒙电商架构中的选型逻辑。
posted @ 2026-01-22 12:25  鸿蒙-散修  阅读(0)  评论(0)    收藏  举报