Day 10 - 继承与多态
目标:掌握ArkTS的继承机制、方法重写、多态与抽象类
预计时间:2-2.5小时
前置知识:Day 09 类基础(属性、构造函数、访问修饰符、static、enum)
第一部分:继承基础(对标 C++ : public Base)
1.1 为什么需要继承
问题引入:假设要开发一个智能家居系统,有智能灯、智能空调、智能门锁等设备。每个设备都有id、name、isOnline状态,都要支持turnOn()和turnOff()操作。如果不使用继承,代码会怎样?
// 没有继承:大量重复代码
class SmartLight {
id: number;
name: string;
isOnline: boolean = false;
brightness: number = 100;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
turnOn(): void {
this.isOnline = true;
console.log(`${this.name} 已开启`);
}
turnOff(): void {
this.isOnline = false;
console.log(`${this.name} 已关闭`);
}
}
class SmartAC {
id: number;
name: string;
isOnline: boolean = false;
temperature: number = 26;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
turnOn(): void {
this.isOnline = true;
console.log(`${this.name} 已开启`);
}
turnOff(): void {
this.isOnline = false;
console.log(`${this.name} 已关闭`);
}
}
// id, name, isOnline, turnOn(), turnOff() 完全重复!
解决方案:使用继承提取公共代码到父类。
1.2 extends 语法
ArkTS使用extends关键字实现类继承:
// 父类(基类):提取公共属性和方法
class Device {
id: number;
name: string;
isOnline: boolean = false;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
turnOn(): void {
this.isOnline = true;
console.log(`${this.name} 已开启`);
}
turnOff(): void {
this.isOnline = false;
console.log(`${this.name} 已关闭`);
}
getStatus(): string {
return this.isOnline ? "在线" : "离线";
}
}
// 子类(派生类):继承父类,添加特有功能
class SmartLight extends Device {
brightness: number = 100;
color: string = "white";
constructor(id: number, name: string, color: string = "white") {
super(id, name); // 调用父类构造函数
this.color = color;
}
setBrightness(level: number): void {
this.brightness = Math.max(0, Math.min(100, level));
console.log(`${this.name} 亮度设置为 ${this.brightness}%`);
}
}
class SmartAC extends Device {
temperature: number = 26;
mode: string = "cool";
constructor(id: number, name: string) {
super(id, name);
}
setTemperature(temp: number): void {
this.temperature = temp;
console.log(`${this.name} 温度设置为 ${this.temperature}°C`);
}
}
// 使用
let light = new SmartLight(1001, "客厅灯", "warm");
light.turnOn(); // 调用继承的方法
light.setBrightness(80); // 调用子类特有方法
console.log(light.getStatus()); // "在线"
let ac = new SmartAC(2001, "主卧空调");
ac.turnOn();
ac.setTemperature(24);
对比C++:
// C++ 继承语法
class SmartLight : public Device { // 必须指定 public/protected/private
// ...
};
关键区别:
- C++支持多重继承(
class D : public A, public B) - ArkTS只支持单继承,一个类只能extends一个父类
- ArkTS只有公有继承,相当于C++的
public继承
1.3 super 调用父类构造函数
问题引入:子类有自己的属性需要初始化,同时又要初始化继承自父类的属性,怎么办?
class SmartLock extends Device {
password: string;
isLocked: boolean = true;
constructor(id: number, name: string, password: string) {
// 必须先调用 super() 初始化父类部分
super(id, name);
// 然后初始化子类自己的属性
this.password = password;
}
unlock(inputPassword: string): boolean {
if (inputPassword === this.password) {
this.isLocked = false;
console.log(`${this.name} 已解锁`);
return true;
}
console.log(`${this.name} 密码错误`);
return false;
}
}
重要规则:
- 子类构造函数必须先调用
super(),然后才能使用this super()必须在构造函数的第一行super()的参数传递给父类构造函数
对比C++:
class SmartLock : public Device {
std::string password;
bool isLocked = true;
public:
SmartLock(int id, const std::string& name, const std::string& pwd)
: Device(id, name), password(pwd) {} // 初始化列表
};
1.4 继承中的属性访问
问题引入:子类能访问父类的哪些成员?private成员能访问吗?
class BaseDevice {
public id: number; // 任何地方可访问
protected firmware: string; // 类内部和子类可访问
private secretKey: string; // 仅类内部可访问
constructor(id: number, key: string) {
this.id = id;
this.firmware = "v1.0";
this.secretKey = key;
}
}
class DerivedDevice extends BaseDevice {
version: string;
constructor(id: number, key: string) {
super(id, key);
this.version = "2.0";
}
checkAccess(): void {
console.log(`ID: ${this.id}`); // ✅ OK,public
console.log(`固件: ${this.firmware}`); // ✅ OK,protected
// console.log(this.secretKey); // ❌ 错误,private
}
}
let device = new DerivedDevice(1001, "secret123");
console.log(device.id); // ✅ OK,public
// console.log(device.firmware); // ❌ 错误,protected,外部不可访问
// console.log(device.secretKey); // ❌ 错误,private
访问权限总结:
| 修饰符 | 父类内部 | 子类内部 | 外部代码 |
|---|---|---|---|
| public | ✅ | ✅ | ✅ |
| protected | ✅ | ✅ | ❌ |
| private | ✅ | ❌ | ❌ |
第二部分:super 关键字深入
2.1 super() 调用父类构造函数
问题引入:如果子类不需要额外初始化,可以省略构造函数吗?
class SimpleDevice extends Device {
// 没有定义构造函数
// 编译器会自动生成:
// constructor(id: number, name: string) {
// super(id, name);
// }
}
// 可以直接使用父类的构造函数参数
let simple = new SimpleDevice(3001, "简单设备");
simple.turnOn();
但如果子类有自定义属性,就必须显式定义构造函数:
class ComplexDevice extends Device {
features: string[];
constructor(id: number, name: string, features: string[]) {
// 在 super() 之前:不能使用 this 访问任何类成员
// this.features = features; // ❌ 错误!不能使用 this
// ✅ 允许:不使用 this 的语句
let temp = id + 1000;
console.log("正在创建设备,ID偏移后:" + temp);
// 必须先调用 super() 初始化父类部分
super(id, name);
// super() 之后才能使用 this 访问类成员
this.features = features;
}
}
为什么要这样设计?
对象构造顺序:先执行父类构造函数(初始化父类部分),再执行子类构造函数(初始化子类部分)。
必须先完成父类部分的构造,子类才能安全地访问继承来的成员。如果允许在 super() 之前使用 this,可能会访问到未初始化的父类成员。
核心规则:super() 不必是第一行代码,但必须在任何 this.xxx 访问之前。
2.2 super.method() 调用父类方法
问题引入:子类重写了父类方法,但想在重写版本中先执行父类的逻辑,怎么办?
class LoggingDevice extends Device {
logEnabled: boolean = true;
constructor(id: number, name: string, logEnabled: boolean = true) {
super(id, name);
this.logEnabled = logEnabled;
}
// 重写 turnOn,但先调用父类的 turnOn
turnOn(): void {
if (this.logEnabled) {
console.log(`[日志] ${this.name} 正在开启...`);
}
super.turnOn(); // 调用父类的 turnOn 方法
if (this.logEnabled) {
console.log(`[日志] ${this.name} 开启完成`);
}
}
// 重写 turnOff
turnOff(): void {
if (this.logEnabled) {
console.log(`[日志] ${this.name} 正在关闭...`);
}
super.turnOff();
if (this.logEnabled) {
console.log(`[日志] ${this.name} 关闭完成`);
}
}
}
let logger = new LoggingDevice(4001, "日志设备");
logger.turnOn();
// 输出:
// [日志] 日志设备 正在开启...
// 日志设备 已开启
// [日志] 日志设备 开启完成
对比C++:
class LoggingDevice : public Device {
public:
void turnOn() override {
std::cout << "[日志] 正在开启..." << std::endl;
Device::turnOn(); // C++ 使用 BaseClass::method()
std::cout << "[日志] 开启完成" << std::endl;
}
};
第三部分:方法重写(对标 C++ virtual 函数覆盖)
3.1 基本方法重写
问题引入:不同类型的设备,getStatus()返回的信息应该不同。如何让子类提供自己的实现?
class Device {
id: number;
name: string;
isOnline: boolean = false;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
turnOn(): void {
this.isOnline = true;
}
turnOff(): void {
this.isOnline = false;
}
// 父类提供一个默认实现
getStatus(): string {
return `[设备] ${this.name}: ${this.isOnline ? "在线" : "离线"}`;
}
}
class SmartLight extends Device {
brightness: number = 100;
// 重写 getStatus 方法
getStatus(): string {
return `[智能灯] ${this.name}: ${this.isOnline ? "在线" : "离线"}, 亮度${this.brightness}%`;
}
}
class SmartAC extends Device {
temperature: number = 26;
// 重写 getStatus 方法
getStatus(): string {
return `[空调] ${this.name}: ${this.isOnline ? "在线" : "离线"}, 温度${this.temperature}°C`;
}
}
// 使用
let light = new SmartLight(1001, "客厅灯");
light.brightness = 80;
console.log(light.getStatus()); // [智能灯] 客厅灯: 离线, 亮度80%
let ac = new SmartAC(2001, "主卧空调");
ac.temperature = 24;
console.log(ac.getStatus()); // [空调] 主卧空调: 离线, 温度24°C
对比C++:
class Device {
public:
virtual std::string getStatus() const { // C++ 需要 virtual 关键字
return "[设备] " + name;
}
};
class SmartLight : public Device {
public:
std::string getStatus() const override { // C++11 使用 override
return "[智能灯] " + name;
}
};
关键区别:
- C++需要显式标记
virtual才能实现多态 - ArkTS所有方法默认可重写,不需要virtual关键字
3.2 重写规则
问题引入:子类重写方法时有什么限制?
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
// 方法可以被重写
makeSound(): string {
return "动物发出声音";
}
// private 方法不能被重写
private secret(): void {
console.log("秘密");
}
}
class Dog extends Animal {
breed: string;
constructor(name: string, breed: string) {
super(name);
this.breed = breed;
}
// ✅ 正确重写:参数相同,返回类型相同
makeSound(): string {
return "汪汪汪";
}
// ❌ 错误:不能重写 private 方法
// secret(): void { }
}
class Cat extends Animal {
// ✅ 正确重写
makeSound(): string {
return "喵喵喵";
}
}
重写规则:
| 规则 | 说明 | 违反后果 |
|---|---|---|
| 方法名相同 | 必须与父类方法名完全一致 | 不是重写 |
| 参数列表相同 | 类型、数量、顺序必须相同 | 类型不兼容,编译错误 |
| 返回类型相同或协变 | 必须相同或是子类型 | 类型不兼容,编译错误 |
| 不能重写 private 方法 | private 方法仅在类内部可见 | 编译错误 |
特殊规则:void 返回值
如果父类方法返回 void,子类方法返回任意类型都构成重写:
class Animal {
makeSound(): void { }
}
class Dog extends Animal {
makeSound(): string { return "汪汪"; } // ✅ 构成重写(特殊规则)
}
注意:虽然编译通过,但会破坏类型安全,不建议这样写。
什么是协变?
协变 = IS-A 关系:子类返回类型必须是父类返回类型的子类型。
class AnimalInfo { name: string = ""; }
class DogInfo extends AnimalInfo { breed: string = ""; }
class Animal {
getInfo(): AnimalInfo { return new AnimalInfo(); }
}
class Dog extends Animal {
getInfo(): DogInfo { // ✅ 协变:DogInfo IS-A AnimalInfo
return new DogInfo();
}
}
判断标准:子类返回类型 extends 父类返回类型 则构成协变。
3.3 阻止方法重写
问题引入:如何防止子类重写某个方法?
ArkTS 中使用 readonly 修饰符 + 箭头函数:
class Base {
// readonly 箭头函数:阻止重写
readonly finalMethod = (): void => {
console.log("这个方法不能被重写");
}
// 普通方法:可以被重写
normalMethod(): void {
console.log("普通方法");
}
}
class Derived extends Base {
// ❌ 错误:无法重写 readonly 箭头函数
// finalMethod = (): void => { }
// ✅ 可以重写普通方法
normalMethod(): void {
console.log("子类重写");
}
}
原理:
readonly属性只能在声明时或构造函数中初始化- 箭头函数作为属性值,子类无法覆盖
- 达到"阻止重写"的效果
对比 C++:
| 语言 | 阻止重写语法 | 原理 |
|---|---|---|
| C++ | void method() final; |
final 关键字,编译器禁止虚函数表覆盖 |
| ArkTS | readonly method = () => {} |
readonly + 箭头函数,属性不可变机制 |
本质:ArkTS 用 "readonly 属性不可变" 模拟了 C++ 的 "final 方法不可重写"。
3.4 调用被覆盖的父类方法
问题引入:子类重写方法后,还能调用父类的原始实现吗?
class Vehicle {
brand: string;
speed: number = 0;
constructor(brand: string) {
this.brand = brand;
}
accelerate(amount: number): void {
this.speed += amount;
console.log(`${this.brand} 加速到 ${this.speed} km/h`);
}
getInfo(): string {
return `${this.brand}: ${this.speed} km/h`;
}
}
class ElectricCar extends Vehicle {
batteryLevel: number = 100;
constructor(brand: string) {
super(brand);
}
// 重写 accelerate,添加电量检查
accelerate(amount: number): void {
if (this.batteryLevel <= 0) {
console.log(`${this.brand} 电量不足,无法加速`);
return;
}
// 调用父类的 accelerate 完成速度增加
super.accelerate(amount);
// 子类特有的逻辑:耗电
this.batteryLevel -= amount * 0.1;
console.log(`${this.brand} 剩余电量: ${this.batteryLevel.toFixed(1)}%`);
}
// 重写 getInfo,但复用父类实现
getInfo(): string {
let baseInfo = super.getInfo(); // 调用父类的 getInfo
return `${baseInfo}, 电量${this.batteryLevel.toFixed(1)}%`;
}
}
let tesla = new ElectricCar("特斯拉");
tesla.accelerate(50); // 先调用 Vehicle.accelerate,然后耗电
console.log(tesla.getInfo());
// 输出:
// 特斯拉 加速到 50 km/h
// 特斯拉 剩余电量: 95.0%
// 特斯拉: 50 km/h, 电量95.0%
第四部分:多态(对标 C++ 虚函数多态)
4.1 什么是多态
问题引入:有一个设备数组,里面包含智能灯、空调、门锁等不同类型的设备。如何统一处理它们?
// 父类引用可以指向子类实例
let devices: Device[] = [
new SmartLight(1001, "客厅灯", "white"),
new SmartAC(2001, "主卧空调"),
new SmartLock(3001, "大门锁", "123456")
];
// 统一处理不同类型的设备
for (let i = 0; i < devices.length; i++) {
let device = devices[i];
console.log(device.getStatus()); // 调用各自重写的 getStatus
}
// 输出:
// [智能灯] 客厅灯: 离线, 亮度100%
// [空调] 主卧空调: 离线, 温度26°C
// [设备] 大门锁: 离线 (SmartLock 没有重写 getStatus,使用父类版本)
多态的核心:父类类型的变量可以引用子类对象,调用方法时会根据实际对象类型执行对应的方法实现。
对比C++:
std::vector<std::unique_ptr<Device>> devices;
devices.push_back(std::make_unique<SmartLight>(1001, "客厅灯"));
devices.push_back(std::make_unique<SmartAC>(2001, "空调"));
for (const auto& device : devices) {
std::cout << device->getStatus() << std::endl; // 虚函数多态
}
4.2 多态实际应用
问题引入:如何设计一个设备管理器,能够统一管理各种设备?
class DeviceManager {
private devices: Device[] = [];
addDevice(device: Device): void {
this.devices.push(device);
console.log(`添加设备: ${device.name}`);
}
// 批量开启所有设备
turnOnAll(): void {
console.log("=== 开启所有设备 ===");
for (let i = 0; i < this.devices.length; i++) {
this.devices[i].turnOn();
}
}
// 批量关闭所有设备
turnOffAll(): void {
console.log("=== 关闭所有设备 ===");
for (let i = 0; i < this.devices.length; i++) {
this.devices[i].turnOff();
}
}
// 获取所有设备状态
getAllStatus(): string[] {
let result: string[] = [];
for (let i = 0; i < this.devices.length; i++) {
result.push(this.devices[i].getStatus());
}
return result;
}
}
// 使用
let manager = new DeviceManager();
manager.addDevice(new SmartLight(1001, "客厅灯", "warm"));
manager.addDevice(new SmartAC(2001, "主卧空调"));
manager.addDevice(new SmartLock(3001, "大门锁", "123456"));
manager.turnOnAll();
let statuses = manager.getAllStatus();
for (let i = 0; i < statuses.length; i++) {
console.log(statuses[i]);
}
4.3 instanceof 类型判断
问题引入:当遍历设备数组时,如何知道某个设备具体是智能灯还是空调?
function processDevice(device: Device): void {
console.log(`处理设备: ${device.name}`);
// 使用 instanceof 判断具体类型
if (device instanceof SmartLight) {
// 在这个分支中,TypeScript 知道 device 是 SmartLight 类型
console.log(`这是一个智能灯,当前亮度: ${device.brightness}%`);
device.setBrightness(80); // 可以调用 SmartLight 特有方法
} else if (device instanceof SmartAC) {
console.log(`这是一个空调,当前温度: ${device.temperature}°C`);
device.setTemperature(24); // 可以调用 SmartAC 特有方法
} else if (device instanceof SmartLock) {
console.log(`这是一个智能锁,锁定状态: ${device.isLocked}`);
} else {
console.log("这是一个普通设备");
}
}
// 使用
let myDevices: Device[] = [
new SmartLight(1001, "客厅灯", "white"),
new SmartAC(2001, "主卧空调"),
new SmartLock(3001, "大门锁", "123456")
];
for (let i = 0; i < myDevices.length; i++) {
processDevice(myDevices[i]);
}
对比C++:
void processDevice(Device* device) {
if (SmartLight* light = dynamic_cast<SmartLight*>(device)) {
// C++ 使用 dynamic_cast 进行运行时类型检查
std::cout << "智能灯亮度: " << light->brightness << std::endl;
} else if (SmartAC* ac = dynamic_cast<SmartAC*>(device)) {
std::cout << "空调温度: " << ac->temperature << std::endl;
}
}
4.4 instanceof 结合类型收窄
问题引入:如何判断后安全地访问子类特有属性?
class SmartFan extends Device {
speed: number = 0;
maxSpeed: number = 3;
constructor(id: number, name: string) {
super(id, name);
}
setSpeed(level: number): void {
this.speed = Math.max(0, Math.min(this.maxSpeed, level));
console.log(`${this.name} 风速设置为 ${this.speed}`);
}
}
function adjustDevice(device: Device): void {
// 先判断类型
if (device instanceof SmartLight) {
// 在这个代码块中,device 被收窄为 SmartLight 类型
console.log(`调整智能灯亮度`);
device.setBrightness(50); // 安全调用 SmartLight 方法
console.log(`当前亮度: ${device.brightness}`);
} else if (device instanceof SmartAC) {
// device 被收窄为 SmartAC 类型
console.log(`调整空调温度`);
device.setTemperature(26);
console.log(`当前温度: ${device.temperature}`);
} else if (device instanceof SmartFan) {
// device 被收窄为 SmartFan 类型
console.log(`调整风扇风速`);
device.setSpeed(2);
console.log(`当前风速: ${device.speed}`);
}
}
// 测试
let testDevices: Device[] = [
new SmartLight(1001, "客厅灯", "white"),
new SmartAC(2001, "主卧空调"),
new SmartFan(4001, "风扇")
];
for (let i = 0; i < testDevices.length; i++) {
adjustDevice(testDevices[i]);
}
类型收窄:在if (x instanceof Class)代码块内部,编译器会自动将变量类型收窄为对应的子类类型,可以安全访问子类特有成员。
第五部分:抽象类(对标 C++ 纯虚函数)
5.1 为什么需要抽象类
问题引入:Device类本身应该被实例化吗?一个纯粹的"设备"是什么?
// 直接实例化 Device 有意义吗?
let genericDevice = new Device(9999, "某个设备");
// 这种"通用设备"在实际业务中可能没有意义
// 我们真正需要的是具体的设备:灯、空调、门锁...
解决方案:使用抽象类,让父类不能被直接实例化,只能被继承。
5.2 abstract class 语法
// 抽象类:不能被 new,只能被继承
abstract class AbstractDevice {
id: number;
name: string;
isOnline: boolean = false;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
turnOn(): void {
this.isOnline = true;
console.log(`${this.name} 已开启`);
}
turnOff(): void {
this.isOnline = false;
console.log(`${this.name} 已关闭`);
}
// 抽象方法:子类必须实现
abstract getDeviceType(): string;
abstract getDetailedStatus(): string;
}
// ❌ 错误:不能实例化抽象类
// let device = new AbstractDevice(1001, "设备"); // 编译错误
// ✅ 正确:创建具体子类继承抽象类
class SmartBulb extends AbstractDevice {
brightness: number = 100;
constructor(id: number, name: string) {
super(id, name);
}
// 必须实现抽象方法
getDeviceType(): string {
return "SmartBulb";
}
getDetailedStatus(): string {
return `${this.name}: 亮度${this.brightness}%, 状态${this.isOnline ? "在线" : "离线"}`;
}
}
class AirConditioner extends AbstractDevice {
temperature: number = 26;
constructor(id: number, name: string) {
super(id, name);
}
getDeviceType(): string {
return "AirConditioner";
}
getDetailedStatus(): string {
return `${this.name}: 温度${this.temperature}°C, 状态${this.isOnline ? "在线" : "离线"}`;
}
}
// 使用
let bulb = new SmartBulb(1001, "客厅灯泡");
console.log(bulb.getDeviceType()); // SmartBulb
console.log(bulb.getDetailedStatus());
5.3 abstract 方法与 abstract 属性
问题引入:如何强制子类实现某些方法?
抽象方法
语法:使用 abstract 关键字声明没有实现的方法。
abstract class Shape {
// 抽象属性:子类必须实现
abstract name: string;
// 抽象方法:只有声明,没有实现
abstract calculateArea(): number;
abstract calculatePerimeter(): number;
// 具体方法:可以有实现
getDescription(): string {
return `${this.name}: 面积=${this.calculateArea()}, 周长=${this.calculatePerimeter()}`;
}
}
class Rectangle extends Shape {
name: string = "矩形";
width: number;
height: number;
constructor(width: number, height: number) {
super();
this.width = width;
this.height = height;
}
// 必须实现抽象方法
calculateArea(): number {
return this.width * this.height;
}
calculatePerimeter(): number {
return 2 * (this.width + this.height);
}
}
class Circle extends Shape {
name: string = "圆形";
radius: number;
constructor(radius: number) {
super();
this.radius = radius;
}
calculateArea(): number {
return Math.PI * this.radius * this.radius;
}
calculatePerimeter(): number {
return 2 * Math.PI * this.radius;
}
}
// 使用多态
let shapes: Shape[] = [
new Rectangle(10, 5),
new Circle(7)
];
for (let i = 0; i < shapes.length; i++) {
console.log(shapes[i].getDescription());
}
// 输出:
// 矩形: 面积=50, 周长=30
// 圆形: 面积=153.9380..., 周长=43.9822...
规则:
- 抽象类不能实例化:
new Shape()会编译错误 - 子类必须实现所有抽象方法,否则子类也必须声明为 abstract
- 抽象方法不能有方法体,只有声明
对比 C++:
| 特性 | C++ | ArkTS |
|---|---|---|
| 抽象类 | 含纯虚函数的类 | abstract class |
| 纯虚函数 | virtual void f() = 0 |
abstract f(): void |
| 能否实例化 | ❌ 不能 | ❌ 不能 |
| 必须实现 | ✅ 是 | ✅ 是 |
抽象属性(ArkTS 特有,C++ 无此特性)
为什么需要抽象属性?
考虑场景:父类希望每个子类类型都有一个固定的特征值。
方案一:普通属性 + 构造函数(每个实例值不同)
abstract class Animal {
name: string;
constructor(n: string) {
this.name = n; // 由外部传入
}
}
class Dog extends Animal {
constructor() {
super("狗"); // 可以传入不同值
}
}
方案二:普通属性直接初始化(有风险)
abstract class Shape {
name: string = "形状"; // 直接初始化
}
class Rectangle extends Shape {
// 忘记重写 name,导致所有形状都叫"形状"
}
❌ 风险:子类可能忘记重写,导致所有子类使用相同的默认值。
方案三:抽象属性(最佳方案)
abstract class Shape {
abstract name: string; // 无默认值,强制子类实现
getDescription(): string {
return `这是一个${this.name}`;
}
}
class Rectangle extends Shape {
name: string = "矩形"; // 必须实现,编译器检查
}
class Circle extends Shape {
name: string = "圆形"; // 必须实现,编译器检查
}
✅ 优势:
- 强制子类定义(编译器检查)
- 同一子类的所有实例有相同的特征值
- 不会因为忘记重写而出错
与 C++ 的区别:
| 特性 | C++ | ArkTS |
|---|---|---|
| 抽象方法 | virtual void f() = 0 |
abstract f(): void |
| 抽象属性 | ❌ 不支持 | ✅ 支持 |
C++ 只能通过纯虚函数 getName() 来模拟,无法像 ArkTS 这样直接声明抽象属性。
abstract class Shape {
// 抽象属性:子类必须实现
abstract name: string;
// 抽象方法:只有声明,没有实现
abstract calculateArea(): number;
abstract calculatePerimeter(): number;
// 具体方法:可以有实现
getDescription(): string {
return `${this.name}: 面积=${this.calculateArea()}, 周长=${this.calculatePerimeter()}`;
}
}
class Rectangle extends Shape {
name: string = "矩形";
width: number;
height: number;
constructor(width: number, height: number) {
super();
this.width = width;
this.height = height;
}
// 必须实现抽象方法
calculateArea(): number {
return this.width * this.height;
}
calculatePerimeter(): number {
return 2 * (this.width + this.height);
}
}
class Circle extends Shape {
name: string = "圆形";
radius: number;
constructor(radius: number) {
super();
this.radius = radius;
}
calculateArea(): number {
return Math.PI * this.radius * this.radius;
}
calculatePerimeter(): number {
return 2 * Math.PI * this.radius;
}
}
// 使用多态
let shapes: Shape[] = [
new Rectangle(10, 5),
new Circle(7)
];
for (let i = 0; i < shapes.length; i++) {
console.log(shapes[i].getDescription());
}
对比C++:
class Shape {
public:
virtual double calculateArea() = 0; // 纯虚函数 = 0
virtual double calculatePerimeter() = 0; // 纯虚函数
virtual std::string getDescription() { // 普通虚函数
return "形状";
}
virtual ~Shape() = default; // 虚析构函数
};
5.4 抽象类中的具体方法
问题引入:抽象类中可以有已经实现的方法吗?
abstract class Payment {
amount: number;
timestamp: number;
constructor(amount: number) {
this.amount = amount;
this.timestamp = Date.now();
}
// 抽象方法:不同支付方式有不同的实现
abstract processPayment(): boolean;
abstract getPaymentMethod(): string;
// 具体方法:所有支付方式通用的逻辑
getReceipt(): string {
return `收据: ${this.getPaymentMethod()} 支付 ${this.amount}元 时间: ${this.timestamp}`;
}
// 具体方法:验证金额
validateAmount(): boolean {
return this.amount > 0;
}
}
class CreditCardPayment extends Payment {
cardNumber: string;
constructor(amount: number, cardNumber: string) {
super(amount);
this.cardNumber = cardNumber;
}
processPayment(): boolean {
if (!this.validateAmount()) {
return false;
}
console.log(`信用卡 ${this.cardNumber} 扣款 ${this.amount}元`);
return true;
}
getPaymentMethod(): string {
return "信用卡";
}
}
class AlipayPayment extends Payment {
accountId: string;
constructor(amount: number, accountId: string) {
super(amount);
this.accountId = accountId;
}
processPayment(): boolean {
if (!this.validateAmount()) {
return false;
}
console.log(`支付宝账号 ${this.accountId} 扣款 ${this.amount}元`);
return true;
}
getPaymentMethod(): string {
return "支付宝";
}
}
// 使用
let payment1: Payment = new CreditCardPayment(100, "6222****1234");
payment1.processPayment();
console.log(payment1.getReceipt()); // 调用抽象类中的具体方法
let payment2: Payment = new AlipayPayment(200, "user@example.com");
payment2.processPayment();
console.log(payment2.getReceipt());
5.5 抽象属性
abstract class Animal {
// 抽象属性:子类必须实现
abstract species: string;
abstract sound: string;
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
makeSound(): void {
console.log(`${this.name} (${this.species}): ${this.sound}`);
}
abstract move(): void;
}
class Dog extends Animal {
species: string = "犬科";
sound: string = "汪汪汪";
breed: string;
constructor(name: string, age: number, breed: string) {
super(name, age);
this.breed = breed;
}
move(): void {
console.log(`${this.name} 在奔跑`);
}
}
class Bird extends Animal {
species: string = "鸟类";
sound: string = "叽叽喳喳";
wingSpan: number;
constructor(name: string, age: number, wingSpan: number) {
super(name, age);
this.wingSpan = wingSpan;
}
move(): void {
console.log(`${this.name} 在飞翔`);
}
}
let dog = new Dog("旺财", 3, "金毛");
dog.makeSound(); // 旺财 (犬科): 汪汪汪
dog.move();
let bird = new Bird("小白", 1, 30);
bird.makeSound(); // 小白 (鸟类): 叽叽喳喳
bird.move();
第六部分:继承设计原则
6.1 IS-A 关系
问题引入:什么时候应该使用继承?
// ✅ 正确的继承:SmartLight IS A Device(智能灯是一种设备)
class SmartLight extends Device {
// ...
}
// ✅ 正确的继承:Dog IS A Animal(狗是一种动物)
class Dog extends Animal {
// ...
}
// ❌ 错误的继承:Battery IS NOT a Device(电池不是一种设备)
// class Battery extends Device {
// // 电池和设备是"HAS-A"关系,不是"IS-A"关系
// }
// ✅ 正确的做法:使用组合(Composition)
class DeviceWithBattery {
device: Device;
batteryLevel: number = 100;
constructor(device: Device) {
this.device = device;
}
checkBattery(): void {
console.log(`${this.device.name} 电量: ${this.batteryLevel}%`);
}
}
IS-A原则:只有当子类确实是父类的一种特殊类型时,才使用继承。
6.2 里氏替换原则(LSP)
问题引入:子类可以替换父类而不影响程序正确性吗?
// 违反 LSP 的例子:正方形和矩形
class Rectangle {
protected _width: number = 0;
protected _height: number = 0;
setWidth(w: number): void {
this._width = w;
}
setHeight(h: number): void {
this._height = h;
}
getArea(): number {
return this._width * this._height;
}
}
// ❌ 违反 LSP:Square 改变了父类的行为
class BadSquare extends Rectangle {
setWidth(w: number): void {
this._width = w;
this._height = w; // 强制相等
}
setHeight(h: number): void {
this._width = h; // 强制相等
this._height = h;
}
}
// 测试函数期望 Rectangle 的行为
function testRectangle(rect: Rectangle): void {
rect.setWidth(5);
rect.setHeight(4);
let area = rect.getArea();
console.log(`期望面积: 20, 实际面积: ${area}`);
// 如果是 BadSquare,面积会是 16,违反预期!
}
// ✅ 正确的做法:让 Square 和 Rectangle 都继承自 Shape
abstract class Shape {
abstract getArea(): number;
}
class GoodRectangle extends Shape {
private width: number;
private height: number;
constructor(width: number, height: number) {
super();
this.width = width;
this.height = height;
}
getArea(): number {
return this.width * this.height;
}
}
class GoodSquare extends Shape {
private side: number;
constructor(side: number) {
super();
this.side = side;
}
getArea(): number {
return this.side * this.side;
}
}
里氏替换原则:子类必须能够替换父类而不改变程序的正确性。
第七部分:小结与练习
知识点对比总结表
| 概念 | C++ | ArkTS |
|---|---|---|
| 继承语法 | class D : public B |
class D extends B |
| 继承类型 | 支持多重继承 | 只支持单继承 |
| 调用父类构造 | 初始化列表 : Base() |
super() 必须在 this 访问之前 |
| 调用父类方法 | Base::method() |
super.method() |
| 方法重写 | 需要 virtual 关键字 |
默认支持,无需 virtual |
| 阻止重写 | final 关键字 |
readonly 箭头函数(属性机制) |
| 纯虚函数 | virtual void f() = 0 |
abstract 类和方法 |
| 类型判断 | dynamic_cast |
instanceof |
| 抽象类 | 含纯虚函数的类 | abstract class |
| 抽象属性 | ❌ 不支持 | ✅ 支持 |
今日练习
练习1:基础继承
// 1. 定义 Animal 基类:
// - 属性:name(string), age(number), isAlive(boolean=true)
// - 构造函数初始化 name 和 age
// - 方法:speak()(输出"动物发出声音")
// - 方法:move()(输出"动物在移动")
// 2. 定义 Dog 类继承 Animal:
// - 新增属性:breed(品种,string)
// - 重写 speak()(输出"汪汪汪")
// - 新增方法:fetch()(输出"狗狗在捡球")
// 3. 定义 Bird 类继承 Animal:
// - 新增属性:wingSpan(翼展,number)
// - 重写 speak()(输出"叽叽喳喳")
// - 重写 move()(输出"鸟儿在飞翔")
// 4. 创建 Animal[] 数组,放入 Dog 和 Bird 实例
// 遍历数组调用 speak() 和 move(),观察多态效果
点击查看答案
class Animal {
name: string;
age: number;
isAlive: boolean = true;
constructor(n: string, a: number) {
this.name = n;
this.age = a;
}
speak(): void {
console.log("动物发出声音");
}
move(): void {
console.log("动物在移动");
}
}
class Dog extends Animal {
breed: string;
constructor(n: string, a: number, b: string) {
super(n, a);
this.breed = b;
}
speak(): void {
console.log("汪汪汪");
}
fetch(): void {
console.log("狗狗在捡球");
}
}
class Bird extends Animal {
wingSpan: number;
constructor(n: string, a: number, w: number) {
super(n, a);
this.wingSpan = w;
}
speak(): void {
console.log("叽叽喳喳");
}
move(): void {
console.log("鸟儿在飞翔");
}
}
function test(): void {
let animals: Animal[] = [];
animals.push(new Dog("来福", 3, "中华田园犬"));
animals.push(new Bird("波利", 2, 2.1));
for (let index = 0; index < animals.length; index++) {
const element = animals[index];
element.speak();
element.move();
}
}
练习2:super 关键字与方法重写
// 1. 定义 Vehicle 基类:
// - 属性:brand(string), speed(number=0)
// - 方法:accelerate(amount: number) 增加速度
// - 方法:brake() 将速度减半
// - 方法:getStatus() 返回状态字符串
// 2. 定义 ElectricCar 继承 Vehicle:
// - 新增属性:batteryLevel(电量,number=100)
// - 重写 accelerate():先检查电量,调用 super.accelerate(),然后耗电(amount * 0.2)
// - 重写 getStatus():包含电量信息,使用 super.getStatus() 获取基础信息
// 3. 定义 GasCar 继承 Vehicle:
// - 新增属性:fuelLevel(油量,number=50)
// - 重写 accelerate():先检查油量,调用 super.accelerate(),然后耗油(amount * 0.1)
// - 重写 getStatus():包含油量信息
// 4. 测试:创建 ElectricCar 和 GasCar 实例,调用 accelerate 和 getStatus
点击查看答案
class Vehicle {
brand: string;
speed: number = 0;
constructor(b: string) {
this.brand = b;
}
accelerate(amount: number): void {
this.speed += amount;
}
brake(): void {
this.speed /= 2;
}
getStatus(): string {
return `brand:${this.brand} speed:${this.speed}`;
}
}
class ElectricCar extends Vehicle {
batteryLevel: number = 100;
accelerate(amount: number): void {
if (this.batteryLevel > 0) {
super.accelerate(amount);
this.batteryLevel -= amount * 0.2;
}
}
getStatus(): string {
return `batteryLevel:${this.batteryLevel} ${super.getStatus()}`;
}
}
class GasCar extends Vehicle {
fuelLevel: number = 50;
accelerate(amount: number): void {
if (this.fuelLevel > 0) {
super.accelerate(amount);
this.fuelLevel -= amount * 0.1;
}
}
getStatus(): string {
return `fuelLevel:${this.fuelLevel} ${super.getStatus()}`;
}
}
function test(): void {
let eCar: ElectricCar = new ElectricCar("BYD");
let gCar: GasCar = new GasCar("BYD");
eCar.accelerate(10);
console.log(`${eCar.getStatus()}`);
gCar.accelerate(10);
console.log(`${gCar.getStatus()}`);
}
练习3:instanceof 类型判断
// 1. 使用练习2中的 Vehicle 类层次结构
// 2. 编写函数 processVehicle(vehicle: Vehicle): void:
// - 打印 vehicle.getStatus()
// - 使用 instanceof 判断具体类型
// - 如果是 ElectricCar,打印当前电量
// - 如果是 GasCar,打印当前油量
// 3. 创建 Vehicle[] 数组,包含 ElectricCar 和 GasCar
// 遍历数组调用 processVehicle
点击查看答案
// 使用练习2中的 Vehicle 类
function processVehicle(vehicle: Vehicle): void {
console.log(`${vehicle.getStatus()}`);
if (vehicle instanceof ElectricCar) {
console.log(`batteryLevel:${vehicle.batteryLevel}`);
} else if (vehicle instanceof GasCar) {
console.log(`fuelLevel:${vehicle.fuelLevel}`);
}
}
function test(): void {
let cars: Vehicle[] = [];
cars.push(new ElectricCar("BYD"));
cars.push(new GasCar("BYD"));
for (let index = 0; index < cars.length; index++) {
const car = cars[index];
processVehicle(car);
}
}
练习4:抽象类
// 1. 定义抽象类 Employee:
// - 属性:id(number), name(string), baseSalary(number)
// - 构造函数初始化以上属性
// - 抽象方法:calculateSalary(): number
// - 抽象方法:getRole(): string
// - 具体方法:getInfo() 返回基本信息字符串
// 2. 定义 SalariedEmployee 继承 Employee:
// - 新增属性:monthlySalary(月薪)
// - 实现 calculateSalary() 返回 monthlySalary
// - 实现 getRole() 返回 "正式员工"
// 3. 定义 HourlyEmployee 继承 Employee:
// - 新增属性:hourlyRate(时薪), hoursWorked(工作小时数)
// - 实现 calculateSalary() 返回 hourlyRate * hoursWorked
// - 实现 getRole() 返回 "小时工"
// 4. 创建 Employee[] 数组,放入不同类型的员工
// 遍历数组打印每个员工的 getInfo() 和 calculateSalary()
点击查看答案
abstract class Employee {
id: number;
name: string;
baseSalary: number;
constructor(id: number, name: string, bs: number) {
this.id = id;
this.name = name;
this.baseSalary = bs;
}
abstract calculateSalary(): number;
abstract getRole(): string;
getInfo(): string {
return `id:${this.id} name:${this.name} role:${this.getRole()} bs:${this.baseSalary}`;
}
}
class SalariedEmployee extends Employee {
monthlySalary: number;
constructor(id: number, name: string, bs: number, ms: number) {
super(id, name, bs);
this.monthlySalary = ms;
}
calculateSalary(): number {
return this.monthlySalary;
}
getRole(): string {
return "正式员工";
}
}
class HourlyEmployee extends Employee {
hourlyRate: number;
hoursWorked: number;
constructor(id: number, name: string, bs: number, hr: number, hw: number) {
super(id, name, bs);
this.hourlyRate = hr;
this.hoursWorked = hw;
}
calculateSalary(): number {
return this.hourlyRate * this.hoursWorked;
}
getRole(): string {
return "小时工";
}
}
function test(): void {
let employees: Employee[] = [];
employees.push(new SalariedEmployee(1, "thomas", 2000, 2500));
employees.push(new HourlyEmployee(2, "Jim", 2000, 22, 8));
for (let index = 0; index < employees.length; index++) {
const employee = employees[index];
console.log(`${employee.getInfo()} salary:${employee.calculateSalary()}`);
}
}
练习5:综合应用
// 设计一个简单的游戏角色系统:
// 1. 定义抽象类 GameCharacter:
// - 属性:name(string), hp(number), maxHp(number)
// - 抽象方法:attack(target: GameCharacter): void
// - 抽象方法:getCharacterType(): string
// - 具体方法:takeDamage(damage: number): void 减少hp
// - 具体方法:isAlive(): boolean 判断hp > 0
// - 具体方法:getStatus(): string 返回角色状态
// 2. 定义 Warrior 继承 GameCharacter:
// - 新增属性:strength(力量,number)
// - 实现 attack():根据 strength 计算伤害
// - 实现 getCharacterType() 返回 "战士"
// 3. 定义 Mage 继承 GameCharacter:
// - 新增属性:mana(魔法值,number)
// - 实现 attack():消耗 mana 造成魔法伤害
// - 实现 getCharacterType() 返回 "法师"
// 4. 创建战斗函数 battle(char1: GameCharacter, char2: GameCharacter):
// - 让两个角色互相攻击,直到一方死亡
// - 使用 instanceof 判断角色类型,打印特殊信息
// 5. 测试:创建 Warrior 和 Mage 实例,进行战斗
点击查看答案
abstract class GameCharacter {
name: string;
hp: number;
maxHp: number;
constructor(name: string, maxHp: number) {
this.name = name;
this.maxHp = maxHp;
this.hp = maxHp;
}
abstract attack(target: GameCharacter): void;
abstract getCharacterType(): string;
takeDamage(damage: number): void {
this.hp -= damage;
if (this.hp < 0) this.hp = 0;
}
isAlive(): boolean {
return this.hp > 0;
}
getStatus(): string {
return `${this.name}[${this.getCharacterType()}] HP:${this.hp}/${this.maxHp}`;
}
}
class Warrior extends GameCharacter {
strength: number;
constructor(name: string, maxHp: number, strength: number) {
super(name, maxHp);
this.strength = strength;
}
attack(target: GameCharacter): void {
let damage = this.strength * 2;
target.takeDamage(damage);
console.log(`${this.name} 用剑攻击,造成 ${damage} 点伤害!`);
}
getCharacterType(): string {
return "战士";
}
}
class Mage extends GameCharacter {
mana: number;
constructor(name: string, maxHp: number, mana: number) {
super(name, maxHp);
this.mana = mana;
}
attack(target: GameCharacter): void {
if (this.mana >= 10) {
this.mana -= 10;
let damage = 25;
target.takeDamage(damage);
console.log(`${this.name} 释放火球术,造成 ${damage} 点伤害!剩余法力:${this.mana}`);
} else {
console.log(`${this.name} 法力不足,攻击失败!`);
}
}
getCharacterType(): string {
return "法师";
}
}
function battle(char1: GameCharacter, char2: GameCharacter): void {
console.log("=== 战斗开始 ===");
console.log(char1.getStatus());
console.log(char2.getStatus());
let round = 1;
while (char1.isAlive() && char2.isAlive()) {
console.log(`\n--- 第 ${round} 回合 ---`);
// char1 攻击 char2
if (char1 instanceof Warrior) {
console.log("战士发起猛烈攻击!");
} else if (char1 instanceof Mage) {
console.log("法师开始施法...");
}
char1.attack(char2);
console.log(char2.getStatus());
if (!char2.isAlive()) break;
// char2 攻击 char1
char2.attack(char1);
console.log(char1.getStatus());
round++;
}
console.log(`\n=== 战斗结束 ===`);
console.log(char1.isAlive() ? `${char1.name} 获胜!` : `${char2.name} 获胜!`);
}
function test(): void {
let warrior = new Warrior("亚瑟", 100, 15);
let mage = new Mage("甘道夫", 80, 50);
battle(warrior, mage);
}

浙公网安备 33010602011771号