散修带你入门鸿蒙应用开发基础第十二节:继承基础与多态入门
第十二节:继承基础与多态入门
炼气十二重天
【学习目标】
- 理解继承的核心价值(代码复用+逻辑扩展),掌握ArkTS中
extends实现类继承的标准语法; - 吃透
super关键字的双重用法(调用父类构造、调用父类方法),规避子类构造函数/方法的常见错误; - 掌握
protected修饰符的访问规则,理解其在父子类数据共享中的核心作用(对比public/private); - 理解多态的本质(父类引用指向子类对象),实现鸿蒙电商场景下多类型商品的统一管理;
- 识别继承的使用边界,避免“过度继承”导致的代码耦合问题。
【学习重点】
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 继承核心语法规则
- 继承语法:
class 子类名 extends 父类名 { ... }; super关键字:子类访问父类的唯一入口:- 构造函数内:
super(父类参数),初始化父类属性,必须是构造函数第一行代码; - 方法内:
super.父类方法(),复用父类逻辑后扩展子类功能;
- 构造函数内:
- 成员访问:子类可继承父类
public/protected成员,private成员仅父类内可访问; - 方法重写:子类可重写父类方法(方法名、参数、返回值一致),实现个性逻辑;
- 属性复用:子类可直接使用或修改父类
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 多态三要素
- 继承:子类继承父类(
PhysicalGoods/VirtualGoods通过extends关键词→Goods); - 方法重写:子类重写父类通用方法(如
printBaseInfo); - 父类引用:用
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 多态的核心优势
- 简化代码:无需大量
if-else判断商品类型,统一通过父类接口调用; - 易扩展:新增“秒杀商品”子类时,仅需继承
Goods,无需修改列表管理代码; - 符合开闭原则:扩展新功能不修改原有代码,降低维护成本。
六、常见问题解答
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 如何避免继承导致的代码耦合?
- 单一职责:父类仅封装通用逻辑,不包含子类专属功能;
- 优先组合:如促销逻辑通过引用
Promotion类实现,而非继承; - 少暴露
protected属性:优先提供方法(如get basePrice()),降低子类对父类实现的依赖。
七、课堂小结
- 继承通过
extends实现“共性复用、个性扩展”,子类构造函数必须优先调用super(); super是父子类交互的唯一入口:构造函数内初始化父类属性,方法内复用父类逻辑;protected修饰符平衡“共享性”与“安全性”,public属性(如profitRate)适配灵活配置场景;- 多态的核心是“父类引用指向子类对象”,实现多类型商品的统一管理,提升代码扩展性;
- 继承解决“复用”,多态解决“统一调用”,二者结合是鸿蒙电商商品管理的核心设计思路;
- 避免过度继承,优先组合而非继承,降低代码耦合度。
八、代码仓库
工程名称:ClassObjectDemo_1
本节代码已同步至:https://gitee.com/juhetianxia321/harmony-os-code-base.git
九、下节预告
- 下一节方法重写与抽象类应用将深入学习:
override显式重写:规范子类方法重写,提升代码可读性;- 抽象类/抽象方法:强制子类实现核心逻辑,避免“空实现”;
- 多态进阶:基于抽象类实现严格的商品规范(如强制子类设置
profitRate);
- 抽象类是鸿蒙大型工程中保证代码规范性的核心手段。
十、鸿蒙开发者学习与认证指引
(一)、官方学习班级报名(免费)
- 班级链接:HarmonyOS赋能资源丰富度建设(第四期)
- 学号填写规则:填写个人手机号码即可完成班级信息登记
(二)、HarmonyOS应用开发者认证考试(免费)
-
考试链接:HarmonyOS开发者能力认证入口
-
认证等级及适配人群
- 基础认证:适配软件工程师、移动应用开发人员,需掌握HarmonyOS基础概念、DevEco Studio基础使用、ArkTS及ArkUI基础开发等能力;
- 高级认证:适配项目经理、工程架构师,需掌握系统核心技术理念、应用架构设计、关键技术开发及应用上架运维等能力;
- 专家认证:适配研发经理、解决方案专家,需掌握分布式技术原理、端云一体化开发、跨端迁移及性能优化等高级能力。
-
认证权益:通过认证可获得电子版证书以及其他专属权益。
浙公网安备 33010602011771号