在鸿蒙应用开发中,ArkTS 作为核心开发语言,其面向对象特性是搭建可维护、可扩展项目的基础。而 "继承" 作为面向对象的核心能力之一,不仅能大幅减少重复代码,还能配合多态实现灵活的功能扩展。今天我们就从基础概念入手,结合真实的员工工资计算场景,拆解 ArkTS 继承的语法规则、实战技巧与避坑要点,帮你彻底掌握这一关键知识点。
一、搞懂继承:"子承父业" 的编程逻辑
提到继承,我们可以用 "子承父业" 来理解 ------ 就像子女能继承父母的部分技能与资源,在 ArkTS 中,子类(子对象)也能自动获取父类(父对象)的非私有属性和方法,无需重复编写相同逻辑。
比如开发企业管理系统时,"员工" 是所有岗位的共同身份:程序员、项目经理、前台都有 "姓名、性别、工资、奖金" 这些共性属性,也都需要 "展示个人信息""计算月收入" 这些共性行为。如果为每个岗位单独写一套代码,后续要新增 "工龄" 属性,就得修改所有岗位的类 ------ 而继承能解决这个问题:把共性内容抽成父类,子类只需关注自身的差异化逻辑。
继承的核心价值主要有三点,也是实际开发中架构设计的常用思路:
- 代码重用:子类直接复用父类的非私有逻辑,减少重复代码量;
- 多态灵活:同一方法在不同子类有不同实现,调用时自动匹配实际对象;
- 模块解耦:结合接口编程降低模块依赖,后续扩展新功能更轻松。
二、ArkTS 继承基础:语法与核心规则
ArkTS 的继承语法与 Java、TypeScript 等主流语言相似,核心是 extends
关键字,但有几个关键规则需要注意,尤其是 "继承范围" 和 "方法重写",这是新手最易踩坑的地方。
1. 基本语法:子类如何继承父类?
我们先定义一个 "员工父类",再让 "程序员子类" 继承它,直观感受语法结构:
typescript
// 父类:员工(Employee),封装所有岗位的共性
export class Employee {
// 成员属性:默认public(公开,子类可继承)
name: string; // 姓名
sex: string; // 性别
sal: number; // 基本工资
comm: number; // 奖金
// 构造方法:初始化属性(ArkTS要求所有字段必须显式初始化)
constructor(name: string, sex: string, sal: number, comm: number) {
this.name = name;
this.sex = sex;
this.sal = sal;
this.comm = comm;
}
// 父类方法:展示员工基本信息
show(): void {
console.log(`姓名:${this.name}, 性别:${this.sex}, 基本工资:${this.sal}, 奖金:${this.comm}`);
}
// 父类方法:计算月收入(默认逻辑:基本工资+奖金)
getIncome(): number {
return this.sal + this.comm;
}
}
// 子类:程序员(Programmer),继承自Employee
export class Programmer extends Employee {
// 子类自动拥有父类的非私有属性(name/sex/sal/comm)和方法(show()/getIncome())
}
2. 关键限制:私有成员 "不可继承"
需要特别注意:父类中用 private
(私有)修饰的成员,子类无法继承和访问。比如曾尝试将 "基本工资(sal)" 设为 private
,结果子类调用该属性时均报错 ------ 最终只能将其改为默认的 public
,才能确保子类正常使用这些属性(原文此处配有报错截图)。
3. 核心能力:子类可 "重写" 父类方法
继承的一大优势是 "子类能改进父类方法",也就是 "方法重写"。比如父类默认的收入计算是 "工资 + 奖金",但程序员有额外的技术补贴,这时就可以在子类中重写 getIncome()
方法,扩展计算逻辑。
就像生活中 "父亲用传统方式卖油饼,子女用自动化设备卖油饼",核心行为一致,但实现方式更优。以程序员为例,重写代码如下:
typescript
export class Programmer extends Employee {
// 重写父类的getIncome(),添加技术补贴
override getIncome(): number {
return this.sal + this.comm + 1500; // 程序员额外加1500元技术补贴
}
}
这里的 override
关键字可选,但显式添加能让代码更规范,明确表示 "该方法是重写父类的实现"。
三、实战案例:用员工工资计算吃透继承与重写
最能体现继承价值的场景,就是 "同一类型下的差异化功能"------ 比如不同岗位的员工,工资计算规则不同。我们以 "4 类员工的收入计算" 为例,完整演示继承的实战用法,所有逻辑和数据均来自实际开发场景的典型设计。
1. 需求背景
公司有 4 类员工,工资计算规则存在差异:
- 程序员:基本工资 + 奖金 + 1500 元(技术补贴);
- 项目经理:基本工资 + 奖金 + 5000 元(管理奖);
- 测试人员:基本工资 + 奖金 + 800 元(测试补贴);
- 前台:基本工资 + 奖金(无额外补贴,沿用父类逻辑)。
2. 子类实现:只关注 "差异化" 逻辑
因为父类已经封装了共性(属性、show()
方法、默认 getIncome()
),子类只需针对 "收入计算" 这一差异化点,重写 getIncome()
即可,代码极简:
typescript
// 1. 项目经理类(Manager)
export class Manager extends Employee {
override getIncome(): number {
return this.sal + this.comm + 5000; // 加5000元管理奖
}
}
// 2. 测试人员类(Tester)
export class Tester extends Employee {
override getIncome(): number {
return this.sal + this.comm + 800; // 加800元测试补贴
}
}
// 3. 前台类(Fronter)
export class Fronter extends Employee {
// 无额外补贴,直接复用父类逻辑(也可显式重写,保持代码统一)
override getIncome(): number {
return super.getIncome(); // super关键字:调用父类的方法
}
}
这里的 super
关键字很实用:当子类想复用父类方法,再补充自己的逻辑时,用 super.方法名()
就能直接调用父类实现,避免重复代码。比如后续前台要加 "全勤奖",只需改成 return super.getIncome() + 300
,灵活又高效。
3. 效果验证:创建对象并计算收入
定义好类后,我们创建不同岗位的员工对象,调用 getIncome()
计算收入,看看继承和重写是否生效,运行后会看到弹窗结果完全符合预期:

这就是继承的优势:父类定好 "框架",子类填充 "差异",代码简洁且易维护。
四、多态:继承的 "进阶技能",让代码更灵活
继承的核心价值之一是实现 "多态"------ 简单说就是 "父类引用指向子类对象,调用方法时取决于实际对象类型"。这一特性能让代码更灵活,尤其适合 "批量处理同一类型下的不同对象"。
1. 多态的具体体现
我们定义一个 Employee
类型的数组,里面存放不同子类的对象,然后遍历数组统一调用 getIncome()
:
typescript
// 父类引用数组,存放不同子类对象
const employees: Employee[] = [
new Programmer('阿朱', '女', 9000, 100),
new Manager('Ken', '男', 40000, 1000),
new Tester('阿紫', '女', 11000, 200),
new Fronter('小丽', '女', 6000, 1000)
];
// 遍历数组,统一计算所有员工总收入
let totalIncome = 0;
employees.forEach(emp => {
totalIncome += emp.getIncome();
console.log(`${emp.name} 的月收入:${emp.getIncome()}元`);
});
console.log(`所有员工月总收入:${totalIncome}元`);
这里的关键是:虽然 emp
的类型是 Employee
,但调用 emp.getIncome()
时,会自动匹配 emp
实际指向的子类(比如 Programmer
或 Manager
),执行对应的重写方法 ------ 这就是多态。
2. 多态的优势:易扩展,符合 "开闭原则"
如果后续公司新增 "钟点工" 岗位,只需新增 PartTimer
子类并重写 getIncome()
,无需修改遍历数组的逻辑:
typescript
// 新增:钟点工类
export class PartTimer extends Employee {
override getIncome(): number {
return this.sal * 22; // 假设钟点工按"日薪×22天"计算
}
}
// 直接添加到数组,无需修改遍历逻辑
employees.push(new PartTimer('老王', '男', 300, 0));
这种 "对扩展开放,对修改关闭" 的特性,是多态的核心价值,也是大型项目能持续迭代的关键。
五、避坑指南:构造方法的 "隐形规则"
在 ArkTS 继承中,构造方法是最容易出错的地方,有两个核心规则必须牢记,否则会频繁报编译错误。
规则 1:子类构造必须先调用父类构造(super)
ArkTS 有个 "隐形要求":子类的构造方法中,必须先调用父类的构造方法(用 super()
),否则会报错。这就像 "孩子出生前,父母必须先存在" 的逻辑。
如果父类有无参构造 (默认或显式定义),子类构造会自动调用 super()
,无需手动写:
typescript
// 父类:显式定义无参构造
export class Employee {
name: string = '';
sex: string = '';
sal: number = 0;
comm: number = 0;
constructor() {} // 无参构造
}
// 子类:构造自动调用super()
export class Programmer extends Employee {
constructor(name: string) {
// 无需手动写super(),ArkTS自动调用父类无参构造
this.name = name; // 初始化父类继承的name属性
}
}
规则 2:父类有有参构造时,子类必须显式传参
如果父类只有有参构造 (没有无参构造),子类必须在构造中显式调用 super(参数)
,传递父类需要的参数,否则会报错:
typescript
// 父类:只有有参构造(无无参构造)
export class Employee {
name: string;
sex: string;
sal: number;
comm: number;
// 有参构造,无无参构造
constructor(name: string, sex: string, sal: number, comm: number) {
this.name = name;
this.sex = sex;
this.sal = sal;
this.comm = comm;
}
}
// 子类:必须显式调用super(参数)
export class Programmer extends Employee {
constructor(name: string, sex: string, sal: number, comm: number) {
super(name, sex, sal, comm); // 必须调用,否则报错
}
}
如果子类想自定义构造参数(比如只传姓名和性别,工资奖金默认 0),也可以在 super
中填默认值:
typescript
export class Programmer extends Employee {
// 子类构造只接收2个参数
constructor(name: string, sex: string) {
// 调用父类有参构造,工资奖金默认0
super(name, sex, 0, 0);
}
}
额外提醒:ArkTS 字段必须初始化
ArkTS 有 "空安全" 特性,要求所有字段(父类和子类)必须在声明时或构造中显式初始化(比如 name: string = ''
或 constructor
中赋值),否则会报错。这是为了避免后续调用时出现空指针问题,和 TypeScript 的严格模式逻辑一致。
六、总结
今天我们从 "概念→语法→实战→多态→避坑",完整梳理了 ArkTS 类继承的核心知识点:
- 继承是 "子承父业",核心价值是代码重用、多态和解耦;
- 用
extends
实现继承,private
成员不可继承,override
可重写父类方法; - 多态通过 "父类引用指向子类对象" 实现,灵活支持功能扩展;
- 子类构造必须调用父类构造(
super
),父类有参构造时需显式传参。
继承是 ArkTS 面向对象的基础,也是后续学习自定义组件、状态管理的关键。掌握这些知识点,能让你在鸿蒙开发中写出更简洁、更易维护的代码。如果觉得有用,欢迎分享给身边一起学习鸿蒙的朋友~
当然也欢迎大家一起系统地学习鸿蒙开发,深入掌握类和对象、继承多态等 ArkTS 面向对象核心技术,拿下鸿蒙基础、高级开发者证书,欢迎加入我的鸿蒙班,一起从入门到精通!班级链接:点击免费进入