零基础鸿蒙应用开发第二十二节:类的继承与多态入门
【学习目标】
- 理解继承的核心意义,掌握ArkTS中
extends关键字的使用规则,区分“单继承”特性在鸿蒙开发中的适配场景; - 掌握
super关键字的核心作用(调用父类构造函数、调用父类方法),规避继承中的常见语法错误; - 吃透
protected访问修饰符的访问范围,掌握其与public/private的差异及在层级类中的应用; - 熟悉方法重写的概念,理解隐式重写与显式重写的区别,掌握
override关键字的规范用法,实现子类对父类方法的自定义扩展; - 理解多态的核心概念(父类引用指向子类对象),掌握鸿蒙电商场景下父类引用管理子类对象的基础用法;
- 发现普通父类的两大局限性:可直接实例化无业务意义的对象、无法强制子类实现差异化核心业务逻辑,为后续学习抽象类做好铺垫,实现鸿蒙电商商户端商品分类的统一管理。
【学习重点】
- ArkTS“单继承”规则与鸿蒙开发的适配性,避免多重继承的语法错误;
super关键字在子类构造函数中的强制调用规则,参数需与父类构造函数完全对齐;protected修饰符的核心应用:子类访问父类的成本价(底价)字段,类外不可访问;- 方法重写的完整逻辑:先掌握隐式重写(无需
override),再理解override的规范价值,明确重写的核心规则(签名一致、访问修饰符约束); - 多态的基础实现:父类引用指向子类对象,简化商户端商品列表的统一管理;
- 普通父类的局限性:①
Goods类可直接实例化,无法约束商品必须属于具体子类(如数码、图书);②父类默认方法实现无法强制子类差异化实现核心业务逻辑(如折后价计算); - 与上一节
Goods类的衔接:新增costPrice属性及对应逻辑,保留原有setPrice/getPrice、printInfo等核心方法,仅重构折后价计算逻辑。
一、工程结构
本节示例代码基于上一节工程,复制一份重命名为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 重写核心规则
- 方法签名必须与父类一致;
- 访问修饰符权限不能缩小;
- 子类重写时可通过
super调用父类原逻辑; - 父类私有方法无法被重写。
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 继承的核心规则
- 单继承规则:ArkTS不支持多重继承,如
class A extends B, C会直接编译报错; - 属性继承规则:子类仅继承父类的
public和protected属性/方法; - 扩展性规则:新增商品类型,仅需继承
Goods类扩展专属逻辑; - 普通父类的潜在问题:
- 可直接实例化无业务意义的纯商品对象;
- 父类的
calculateFinalPrice方法为固定实现,无法强制子类差异化实现; - 隐式重写无编译器校验,易出现“伪重写”导致多态逻辑失效。
五、多态入门
5.1 多态的本质
多态的本质是 “接口统一,实现各异”,父类定义了统一的方法接口,子类通过重写提供不同的实现,通过父类引用调用时,会根据实际指向的子类对象执行对应的实现。
5.2 多态三要素
- 继承:子类继承父类;
- 方法重写:子类重写父类通用方法;
- 父类引用:用
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 多态的核心优势
- 代码简洁:无需用
if-else判断商品类型,统一通过父类接口操作; - 扩展性极强:新增商品类型,无需修改原有批量操作、统计逻辑;
- 显式重写提升稳定性:使用
override关键字可避免“伪重写”; - 暴露普通父类的设计缺陷:
- 可实例化无业务意义的纯商品对象;
- 父类默认方法实现无法强制子类差异化实现核心业务逻辑。
六、常见问题解答
6.1 为什么不建议直接实例化Goods类?
- 从业务逻辑看,商品必须是具体类型,不存在“无类型的纯商品”;
- 从技术角度看,直接实例化会导致:
- 代码约束性差,容易出现不符合业务规则的对象;
- 纯商品对象的
calculateFinalPrice方法使用父类默认实现,无法适配后续差异化的定价逻辑。
6.2 父类引用如何调用子类专属方法?
父类引用需通过类型断言判断类型,符合条件直接调用子类专属方法:
if (book instanceof BookGoods) {
book.getAuthorInfo();
}
6.3 隐式重写和显式重写的区别是什么?
| 对比维度 | 隐式重写 | 显式重写 |
|---|---|---|
| 写法难度 | 简单 | 稍复杂 |
| 可读性 | 差 | 好 |
| 编译器校验 | 无 | 有 |
| 维护成本 | 高 | 低 |
| 工程化推荐度 | 不推荐 | 强烈推荐 |
6.4 为什么父类的calculateFinalPrice方法不适合所有商品类型?
父类的calculateFinalPrice仅实现了“售价×折扣系数”的固定逻辑,但不同商品存在差异化的定价需求,如叠加满减活动、结合会员折扣,该方法无法满足这些多样化的业务需求。
七、内容总结
- 继承的核心是代码复用与逻辑统一,子类通过
extends继承父类,需先调用super()初始化父类必传属性,protected修饰符实现了父子类之间的数据共享; - 方法重写有两种实现方式:隐式重写和显式重写,核心规则是签名一致、访问修饰符不能更严格,可通过
super复用父类逻辑; - 多态通过父类引用指向子类对象实现对不同实体商品的统一管理,显式重写能提升多态逻辑的稳定性,新增商品类型仅需扩展子类,扩展性极强;
- 普通父类存在两大核心局限性:可直接实例化无业务意义的对象、默认方法实现无法强制子类差异化实现核心业务逻辑,这两个问题将在下一节通过抽象类与抽象方法解决。
八、代码仓库
- 工程名称:
ClassObjectDemo_1 - 仓库地址:https://gitee.com/juhetianxia321/harmony-os-code-base.git
九、下节预告
本节我们已经掌握了继承的核心用法、super关键字与protected修饰符的应用,以及方法重写和多态的基础实现逻辑,也明确了普通父类的两大局限性。下一节我们将针对这些问题展开进阶学习,深入探索抽象类的核心知识与实战应用,核心内容包括:
- 抽象类的定义与实战:将
Goods基类改造为AbstractGoods抽象类,从语法层面禁止直接实例化无业务意义的对象; - 抽象方法的声明与实现:将
calculateFinalPrice改为抽象方法,强制子类实现差异化的定价逻辑,解决业务约束问题; - 抽象类+多态的进阶应用:新增商品子类虚拟类商品,实现抽象类引用管理所有商品对象,并结合鸿蒙ArkUI完成商品列表的UI渲染;
- 抽象类与普通类的核心差异:从语法、业务、工程化三个维度对比,明确抽象类在鸿蒙电商架构中的选型逻辑。
浙公网安备 33010602011771号