ArkTS 类与接口概念详解

在 ArkTS 中,类与接口的相关知识是面向对象编程的核心内容。,也是后续开发的重要基础。

类的声明和使用

引入

不妨把类理解成一个模板,用这个模板可以快速创建出具有相同特征和功能的对象。

不妨设想一个实际开发场景。要开发一个控制智能设备的应用,需要管理智能灯、空调、智能锁等设备。如果只用基础变量实现,就需要给每一个设备的每一项信息、每一个功能单独定义。

例如灯光控制,就需要单独定义灯光名称、灯光亮度等变量,还需要一个控制灯光亮度的函数。这样写不仅极其繁琐,还无法直接与设备关联起来。

let name = '灯'; let brightness = 59; let status = 'off'; let location = ''; function setBrightness(brightnessParam: number) { brightness = brightnessParam; } function setStatus(statusParam: string) { status = statusParam; } function setLocation(locationParam: string) { location = locationParam; }

这时候就需要一个容器将同一个设备的信息和功能打包在一起,例如用一个对象包含灯的名称、亮度以及调节亮度的方法。在 ArkTS 中,这种容器就被称为类。

class Light { name = '灯'; brightness = 59; status = 'off'; location = ''; setBrightness(brightnessParam: number) { this.brightness = brightnessParam; } setStatus(statusParam: string) { this.status = statusParam; } setLocation(locationParam: string) { this.location = locationParam; } }

声明

类是面向对象编程的核心载体,用于封装数据(属性)和行为(方法),是创建对象的模板。

通过 class 声明类结构,再用 new 关键字创建类的实例(对象),实现数据与逻辑的模块化组织。
语法规则:使用 class 关键字+类名(首字母大写,遵循命名规范)声明类,类体用 {} 包裹。

class类名{

属性名:类型=属性值

方法名() {

}

}

class Device { name: string = ''; // 定义属性 // 定义方法 getInfo(): string { return `${this.name}`; } }

构造函数与类的实例化

构造函数用 constructor 声明,实例创建时自动执行,用于初始化实例属性。通过 this 可以访问实例的属性和方法。

通过 new() 类名创建类的实例。

class Device { name: string = ''; type: string = 'light'; location: string = 'bedroom'; constructor(name: string, type: string, location: string) { this.name = name; this.type = type; this.location = location; } } let device: Device = new Device('客厅空调', '空调', '客厅'); // 传入参数实例化

如果没有显式定义构造函数,ArkTS 会自动生成一个默认构造函数,会使用字段类型的默认值初始化属性。

class Device { name: string = '客厅空调'; } let device: Device = new Device();

一旦在类中定义了任意自定义构造函数(无论是否有参数),编译器都会停止生成默认构造函数。

class Device { name: string = ''; constructor(name: string) { this.name = name; } } let device: Device = new Device(); // 报错:预期有 1 个参数,但实际未提供任何参数

对象字面量

创建类的实例除了使用 new 关键字进行类的实例化也可以通过对象字面量定义类实例。

对象字面量通过花括号 {} 包裹键值对,快速初始化类实例。对象字面量必须包含类中声明的所有必选属性。不支持对象字面量创建带方法的对象。

class Device { name: string = ''; } // 方式一:new关键字创建实例 let device1 = new Device(); // 方式二:通过对象字面量创建实例 let device2: Device = { name: '智能手表' };
特性对象字面量new 关键字
实例化方式直接通过 {} 初始化属性,无需调用构造函数会调用类的构造函数
构造函数执行不执行类中的构造函数逻辑自动执行构造函数中的初始化逻辑
类型检查严格性属性必须严格匹配类定义(类型、名称一致)自动满足类定义,无需额外类型标注
适用场景轻量级数据模型、无复杂初始化逻辑的简单类需要构造函数逻辑、方法继承的复杂类

属性

类属性是面向对象编程的核心组成部分,用于描述对象的特征。

属性说明
实例属性实例属性是每个实例独立拥有的属性。
静态属性通过 static 关键字声明,访问方式为:类名.属性名。
只读属性只读属性通过 readonly 关键字声明,赋值后不可修改。

实例属性和静态属性的区别很明显,静态属性前面会加 static 关键字,实例属性没有。访问的时候,实例属性必须先创建类的实例,再通过实例名.属性名访问,而静态属性不用创建实例,直接通过类名.属性名访问。

静态属性是属于类本身的,不是属于某个实例的,所以创建出来的实例是访问不到静态属性的。

class Device { name: string = '未知设备'; // 静态属性:所有设备共享的品牌信息 static brand: string = '智能家居'; } // 访问实例属性:实例名.属性名 let light = new Device(); console.log(light.name); // 输出: 未知设备 // 访问静态属性:类名.属性名(无需实例化) console.log(Device.brand); // 输出智能家居 // 静态属性不归属实例,实例无法访问 let device = new Device(); console.log(device.brand) // 报错:实例不存在 brand 属性

只读属性是用 readonly 这个关键字声明的,也就是说这个属性只能在创建实例时赋值,之后就无法修改了。在实际开发中,readonly 常常和 static 一起用。用来定义一些固定不变的类级别的属性。

class Device { name: string = '未知设备'; // 静态属性:所有设备共享的品牌信息 static readonly brand: string = '智能家居'; } let device = new Device(); console.log(device.name); // 未知设备 device.name = '灯'; // 实例属性可重新赋值 console.log(device.name); // 灯 console.log(Device.brand); // 输出智能家居 Device.brand = '其他'; // 无法分配给 'brand',因为它是只读属性
维度变量类属性
定义位置可在任何作用域,通过 let/const 关键字声明类内部声明,通过修饰符修饰
调用方式直接调用通过对象或类名(静态方法)调用
生命周期由作用域决定(如函数执行完毕即销毁)与类实例同生命周期

方法

类的方法是面向对象编程的核心组成部分,用于封装对象的行为和操作。

实例方法是类中定义的普通方法。需通过实例调用,可通过 this 访问实例属性。

静态方法通过 static 关键字声明,归属类而非实例,无需实例化即可调用。

class Device { name: string = '未知设备'; // 实例方法:获取设备完整信息 getInfo(): string { return `设备名称是:${this.name}`; } // 静态方法:验证设备名称是否合法(工具类逻辑) static isValidName(name: string): boolean { return name.trim().length > 0; } } let light = new Device(); light.name = '客厅主灯'; // 调用实例方法:实例名.方法名() console.log(light.getInfo()); // 输出:客厅主灯 (类型:light) // 调用静态方法:类名.方法名() console.log(`${Device.isValidName('客厅主灯')}`); // 输出:true
维度函数方法
定义位置全局/模板作用域,通过 function 关键字声明类内部
调用方式直接调用通过对象或类名(静态方法)调用
访问实例数据通过 this 访问

继承

还是回到智能家居控制应用的开发场景,假设我们现在已经定义了两个类,一个是控制灯光的 Light 类,一个是控制空调的 AirConditioner 类。

class Light { name = '灯'; status = 'off'; location = ''; brightness = 59; setStatus(statusParam: string) { this.status = statusParam; } setLocation(locationParam: string) { this.location = locationParam; } setBrightness(brightnessParam: number) { this.brightness = brightnessParam; } } class AirConditioner { name = '空调'; status = 'off'; location = ''; temperature: number = 26; setStatus(statusParam: string) { this.status = statusParam; } setLocation(locationParam: string) { this.location = locationParam; } setTemperature(temp: number): void { this.temperature = temp; } }

不难发现这两个类中有很多重复内容,例如都有设备名称、开关状态。这些共性属性也都有设置状态,而且后续还要添加更多设备。如果每新增一个设备就要重写一个类,把这些共性的内容再写一遍,就会有大量重复工作,项目中也会出现大量重复代码,既麻烦又容易出错。

因此,可以考虑将这些共性的属性和方法统一抽取到一个公共类里,只需要保证 Light 类既可以直接用这个公共类里面的公共属性和方法,又能保留自己特有的属性和方法即可。

定义一个包含公共属性和方法的 Device 类,再定义 Light 类继承 Device 类。继承使用 extends 关键字,并且只能单继承,即一个类只能有一个父类。通过继承后,Light 类就继承了 Device 类中的属性和方法。加上自己定义的亮度属性和设置亮度的方法,就和之前定义的 Light 类是一样的了。

class Device { name = ''; status = 'off'; location = ''; setStatus(statusParam: string) { this.status = statusParam; } setLocation(locationParam: string) { this.location = locationParam; } } class Light extends Device { brightness = 59; setBrightness(brightnessParam: number) { this.brightness = brightnessParam; } }

super 是子类访问父类构造函数、方法和属性,实现继承逻辑复用的方法。

构造函数规则:子类构造函数中必须首先通过 super() 调用父类构造函数,且必须在访问 this 之前进行。

class Device { name: string; constructor(name: string) { this.name = name; } } // 子类继承 Device class Light extends Device { brightness: number = 50; constructor(name: string, brightness: number) { super(name); // 调用父类构造函数 this.brightness = brightness; } setBrightness(value: number): void { this.brightness = value; console.log(`${this.name},亮度设置为: ${value}%`); } } let light: Light = new Light('客厅灯', 50); light.setBrightness(100);

封装

类的封装是实现代码复用、逻辑内聚和数据安全的核心技术。通过访问修饰符(private、protected、public)限制属性和方法的可见性。

若未明确指定修饰符,ArkTS 默认成员为 public。

修饰符可见范围适用场景
public全局可见(类内部、子类、外部均可访问)公共接口、跨模块调用
private仅类内部(子类、外部均禁止)封装敏感数据或内部逻辑
protected类及子类(同一模块内)允许子类继承并扩展父类功能
class Device { // public 可省略(默认) public name: string; private id: string = '1’; protected status: string = 'off'; constructor(name: string) { this.name = name; } } let light = new Device('客厅灯'); console.log(light.name); // 外部可访问,输出:客厅灯 console.log(light.id); // 报错:属性'id'是私有的,只能在'Device'类内部访问 console.log(light.status); // 报错:属性'status'是私有的,只能在'Device'类内部访问

不妨看看下面这个示例。这里创建了一个 Light 类,通过 extends 继承 Device 类,并在 Light 类中实现了一个设置亮度的方法。在这个方法中可以访问 name 属性和被 protected 修饰的 status 属性,这些在 Light 类内部是可以正常访问的。但如果试图在子类访问父类的私有属性时,编译器会提示错误信息,属性 id 是私有的,只能在 Device 类内部访问。创建 Light 的实例后,通过这个实例也能访问到父类定义的 name 属性和公共方法。不过通过 Light 实例访问 status 属性是行不通的,因为 status 是被 protected 修饰的,只能在父类内部和子类内部访问,类外部不能访问。

class Device { // public 可省略(默认) public name: string; private id: string = '1’; protected status: string = 'off'; constructor(name: string) { this.name = name; } } // 子类继承 Device class Light extends Device { private brightness: number = 50; constructor(name: string) { super(name); } setBrightness(value: number): void { if (this.status === 'on') { this.brightness = value; console.log(`${this.name}开关状态为${this.status}亮度设置为:${value}`); } else { console.log(`${this.name}开关状态为${this.status}未开启,无法设置亮度`); } } public getInfo(): string { // return `${this.name} 设备id为${this.id}; // 属性'id'是私有的,只能在'Device'类内部访问。 } } let light = new Light('客厅灯'); console.log(light.name); // 打印:客厅灯 light.setBrightness(50) // 打印:客厅灯开关状态为off未开启,无法设置亮度 // console.log(light.status); // 报错:类型'Light'上不存在属性'status'。

多态

类的多态是指同一行为(方法)在不同对象上有不同表现。子类继承父类,并可以重写父类方法。不同的子类对父类方法的重写逻辑不一样。通过统一的父类接口调用时,会呈现出不同的效果。

方法重写时要求子类方法与父类方法同名,且参数类型顺序及数量完全一致,返回值要与父类相同或者是其子类型,并且访问权限不能缩小。

class Device { public name: string; protected status: string = 'off'; constructor(name: string) { this.name = name; } // 父类统一方法(子类可重写) turnOn(): void { console.log(`${this.name}状态为${this.status}`); } } // 灯光设备 class Light extends Device { private brightness: number = 50; turnOn(): void { this.status = 'on'; console.log(`${this.name}状态为${this.status},亮度为${this.brightness}`); } } // 空调设备 class AirConditioner extends Device { private temperature: number = 26; turnOn(): void { this.status = 'on'; console.log(`${this.name}状态为${this.status},设置的温度是${this.temperature}`); } } let light: Device = new Light('客厅灯'); light.turnOn(); // 输出:客厅灯状态为 on,亮度为 50 let airConditioner: Device = new AirConditioner('空调'); airConditioner.turnOn(); // 输出:空调状态为 on,设置的温度是 26

抽象类

抽象类通过 abstract class 声明,是无法直接实例化的“规范类”,作为父类被子类继承并实现抽象方法。用于定义子类必须遵循的接口标准。

abstract class Device { public name: string = ''; protected abstract status: string; // 抽象方法:仅声明,无方法体,强制子类实现 abstract turnOn(): void; // 普通方法,子类可直接复用 getStatus(): string { return `${this.name} is ${this.status}`; } } // 子类没有实现父类中的抽象变量或抽象方法 class Light extends Device { // 非抽象类'Light'没有实现从类'Device'继承的抽象成员'status'。 // 非抽象类'Light'没有实现从类'Device'继承的抽象成员'turnOn'。 } // 子类实现抽象类 class Light extends Device { protected status: string = ''; // 必须实现抽象方法 turnOn turnOn(): void { this.status = 'on'; console.log(`${this.name} 灯光开启`); } }

接口的声明和使用

声明

接口的作用是约束类的属性和方法结构。接口通过 interface 关键字声明,描述对象应具备的属性和方法。

interface接口名称{
属性名:类型
方法名:返回值类型
}

interface Switchable { deviceName: string; // 设备名称(必填属性) turnOn(): void; // 打开设备(必填方法) turnOff(): void; // 关闭设备(必填方法) }

继承

接口通过 extends 关键字继承其它接口,一个接口可以继承多个父接口。

interface Switchable { deviceName: string; // 设备名称(必填属性) turnOn(): void; // 打开设备(必填方法) turnOff(): void; // 关闭设备(必填方法) } // 1. 基础设备信息接口:约束设备必须包含的基础属性 interface DeviceBase { name: string; // 设备名称 status: string; // 设备状态 deviceType: string; // 设备类型 } // 2. 设备控制接口:约束设备必须实现的开关功能 interface DeviceController { turnOn(): void; // 开启设备 turnOff(): void; // 关闭设备 getStatus(): string; // 获取设备状态 } // 新接口继承 DeviceBase 和 DeviceController,组合两者的约束 interface IDevice extends DeviceBase, DeviceController{ // 接口通过 extends 继承接口 location: string; // 新增约束:设备位置 }

类继承接口

class AirConditioner implements DeviceBase, DeviceController { name: string = ''; status: string = ''; type: string = ''; constructor(name: string, status: string, type: string) { this.name = name; this.status = status; this.type = type; } turnOn(): void { this.status = 'on'; } turnOff(): void { this.status = 'off'; } getStatus(): string { return `${this.name} 的状态为 ${this.status}`; } } let airConditioner = new AirConditioner('空调', 'on', 'AirConditioner');

类实现接口

类需要通过 implements 实现接口,并完成所有成员的具体实现。

class AirConditioner implements DeviceBase, DeviceController { name: string = ''; status: string = ''; type: string = ''; constructor(name: string, status: string, type: string) { this.name = name; this.status = status; this.type = type; } turnOn(): void { this.status = 'on'; } turnOff(): void { this.status = 'off'; } getStatus(): string { return `${this.name} 的状态为 ${this.status}`; } } let airConditioner = new AirConditioner('空调', 'on', 'AirConditioner');

与抽象类的区别

interface IDevice { name: string; turnOn(): void; } class Light implements IDevice { name: string = ''; turnOn(): void { } } abstract class Device { public name: string = ''; protected abstract status: string; // 抽象方法:仅声明,无方法体,强制子类实现 abstract turnOn(): void; // 普通方法,子类可直接复用 getStatus(): string { return `${this.name} is ${this.status}`; } } class Light extends Device { protected status: string = ''; turnOn(): void { } }
特性接口(Interface)抽象类(Abstract Class)
核心作用定义结构契约,强制类遵循统一规范定义基类模板,提供部分实现并约束子类
成员类型仅含属性声明、方法声明可含属性、普通方法(有实现)、抽象方法
继承/实现类可实现多个接口(多实现)类只能继承一个抽象类(单继承)
静态成员不支持静态属性/方法支持静态属性/方法
访问修饰符成员默认 public(不可显示添加其他修饰符)支持 public/private/protected 修饰符