ArkTS 语言基础 第九节:接口与抽象

炼气九重天

第九节:接口与抽象

【教学目标】
  1. 理解接口(interface)的核心作用:定义"数据结构契约"与"行为规范"
  2. 掌握接口的定义语法,能通过接口约束对象、函数的结构
  3. 理解抽象类与抽象方法的概念,掌握 abstract 关键字的使用规则
  4. 熟练区分接口与抽象类的差异,能在不同场景下正确选择使用
【本节重点】
  • 接口的定义与使用:如何通过接口描述"属性类型"和"方法签名"
  • 接口的核心特性:可选属性、只读属性、接口继承
  • 抽象类与抽象方法:abstract 关键字的作用,抽象类的实例化限制
  • 接口 vs 抽象类:两者的本质差异与典型应用场景

一、接口:定义"契约"的工具

接口(interface)本身不包含具体实现,仅用来定义"结构规范",就像一份"契约",要求遵循它的对象或类必须符合约定的结构(属性、方法的类型和名称)。

比如,定义一个"可移动"的接口 Movable,约定所有"可移动的事物"必须有 speed 属性和 move() 方法,至于具体是"汽车移动"还是"人移动",接口不关心,只要求符合规范。

1.1 接口的定义语法

interface 关键字定义接口,内部可包含属性声明方法签名(仅定义方法名、参数类型、返回值类型,无方法体)。

typescript 复制代码
// 定义"用户"接口:约定用户对象的结构
interface User {
  // 必选属性:必须有该属性,且类型为string
  id: string;
  name: string;
  // 可选属性:用"?"表示,可存在可不存在
  age?: number;
  // 只读属性:用"readonly"表示,初始化后不能修改
  readonly registerTime: string;

  // 方法签名:仅定义方法结构,无实现
  sayHi(): void;
  // 带参数的方法签名
  changeName(newName: string): string;
}
1.2 接口的使用场景

接口主要用于"类型约束",常见场景有两种:约束对象结构、约束函数结构,还可用于约束类的实现。

场景1:约束对象结构

当创建对象时,用接口指定对象的"模板",确保对象的属性、方法符合约定。

typescript 复制代码
// 定义接口
interface User {
  id: string;
  name: string;
  age?: number;
  sayHi(): void;
}

// 创建对象时,遵循User接口约束
let user1: User = {
  id: "001",
  name: "张三",
  // age是可选属性,可省略
  sayHi(): void {
    console.log(`大家好,我是${this.name}`);
  }
};

// 错误示例:缺少必选属性id,不符合User接口约束
let user2: User = {
  name: "李四", // 报错:Property 'id' is missing
  sayHi(): void {}
};
场景2:约束函数结构

接口也可描述函数的"输入(参数)"和"输出(返回值)"类型,作为函数的"类型模板"。

typescript 复制代码
// 定义"加法函数"接口:接收两个number参数,返回number
interface AddFunc {
  (a: number, b: number): number;
}

// 定义函数时,遵循AddFunc接口约束
let add: AddFunc = (x, y) => {
  return x + y;
};

console.log(add(1, 2).toString()); // 3

// 错误示例:参数类型不符合接口约束
let wrongAdd: AddFunc = (x: string, y: number) => { // 报错:x的类型应为number
  return x + y;
};
场景3:约束类的实现(implements

类通过 implements 关键字遵循接口,必须实现接口中定义的所有方法和属性(接口中的属性默认为 public)。

typescript 复制代码
// 定义"可播放"接口
interface Playable {
  play(): void;
  pause(): void;
  duration: number; // 必选属性
}

// 类实现接口:必须实现所有方法和属性
class Audio implements Playable {
  duration: number; // 实现接口的属性

  constructor(duration: number) {
    this.duration = duration;
  }

  // 实现接口的方法
  play(): void {
    console.log("音频播放中");
  }

  pause(): void {
    console.log("音频已暂停");
  }
}
1.3 接口的核心特性
(1)可选属性与只读属性
  • 可选属性 :用 ? 标记,对象中可包含也可省略该属性,适用于"非必需但可能存在"的场景(如用户的"年龄""地址")。
  • 只读属性 :用 readonly 标记,对象初始化后该属性不能被修改,适用于"创建后不可变"的数据(如用户的"注册时间""ID")。
typescript 复制代码
interface Book {
  readonly isbn: string; // 只读属性:初始化后不能改
  title: string;
  author: string;
  publisher?: string; // 可选属性:可省略
}

let book1: Book = {
  isbn: "9787111641247",
  title: "JavaScript高级程序设计",
  author: "尼古拉斯·泽卡斯"
};

book1.title = "JS高级程序设计"; // 正常:非只读属性可修改
book1.isbn = "123456"; // 报错:只读属性不能修改
(2)接口继承

接口可通过 extends 继承另一个或多个接口,实现"规范的复用与扩展",避免重复定义。

typescript 复制代码
// 基础接口:定义"可移动"的规范
interface Movable {
  speed: number;
  move(): void;
}

// 继承Movable,扩展"可停止"的规范
interface Stoppable extends Movable {
  stop(): void; // 新增方法签名
}

// 遵循Stoppable接口:需包含Movable的属性+方法,以及Stoppable的方法
let car: Stoppable = {
  speed: 60,
  move(): void {
    console.log(`以${this.speed}km/h行驶`);
  },
  stop(): void {
    console.log("停车");
  }
};
(3)接口合并

同名接口会自动合并,所有成员会被合并为一个接口(注意避免成员冲突)。

typescript 复制代码
// 定义第一个接口
interface Box {
  width: number;
  height: number;
}

// 定义同名接口(自动合并)
interface Box {
  depth: number; // 新增属性
}

// 合并后:Box包含width、height、depth
let box: Box = {
  width: 10,
  height: 20,
  depth: 15
};

二、抽象类:"半实现半规范"的模板

抽象类(用 abstract 修饰)是包含"抽象方法"的类,它既可以有"具体实现的属性和方法",也可以有"仅定义签名、无实现的抽象方法"。

抽象类的核心作用是作为"父类模板",要求子类必须实现抽象方法,同时提供可复用的具体逻辑。

2.1 抽象类与抽象方法的定义
  • abstract 修饰类,该类即为抽象类。
  • abstract 修饰类中的方法,该方法即为抽象方法(仅签名,无方法体)。
typescript 复制代码
// 抽象类:动物(不能直接实例化)
abstract class Animal {
  // 具体属性:所有动物共有的属性
  name: string;

  // 具体方法:所有动物共有的行为(有实现)
  eat(): void {
    console.log(`${this.name}在吃东西`);
  }

  // 抽象方法:仅定义签名,无实现,要求子类必须重写
  abstract shout(): void;

  // 构造函数:初始化具体属性
  constructor(name: string) {
    this.name = name;
  }
}
2.2 抽象类的使用规则
  1. 抽象类不能直接实例化:抽象类是"模板",不是"具体类型",必须通过子类继承并实现抽象方法后,才能创建子类实例。
  2. 子类必须实现所有抽象方法:如果子类不实现父类的抽象方法,子类也必须定义为抽象类。
typescript 复制代码
// 子类:Dog(非抽象类,必须实现shout方法)
class Dog extends Animal {
  // 实现父类的抽象方法
  shout(): void {
    console.log(`${this.name}汪汪叫`);
  }
}

// 子类:Cat(非抽象类,必须实现shout方法)
class Cat extends Animal {
  // 实现父类的抽象方法
  shout(): void {
    console.log(`${this.name}喵喵叫`);
  }
}

// 正确:创建子类实例
let dog = new Dog("小狗");
dog.eat(); // 输出:小狗在吃东西
dog.shout(); // 输出:小狗汪汪叫

// 错误:抽象类不能直接实例化
let animal = new Animal("动物"); // 报错:Cannot create an instance of an abstract class
2.3 抽象类与接口的结合使用

抽象类可实现接口,将接口的规范与抽象类的复用能力结合(抽象类实现接口时,可不必实现接口的方法,而由子类实现)。

typescript 复制代码
// 定义"可游泳"接口
interface Swimmable {
  swim(): void;
}

// 抽象类实现接口(无需实现接口方法)
abstract class AquaticAnimal extends Animal implements Swimmable {
  // 抽象类可暂不实现接口方法,由子类实现
  abstract swim(): void;
}

// 子类:Fish(实现所有抽象方法)
class Fish extends AquaticAnimal {
  shout(): void {
    console.log(`${this.name}吐泡泡`);
  }

  swim(): void {
    console.log(`${this.name}在水里游`);
  }
}

三、接口 vs 抽象类:核心差异与场景选择

接口和抽象类都能定义"规范",但本质和用途完全不同,需根据场景选择。

对比维度 接口(interface 抽象类(abstract class
本质 仅定义"契约",无任何实现 半实现半契约:有具体属性/方法,也有抽象方法
实例化 不能实例化,只能被"实现"或"约束" 不能实例化,只能被"继承"
继承/实现 一个类可实现多个接口(implements A, B 一个类只能继承一个抽象类(extends
访问修饰符 所有成员默认公开(无 private/protected 可使用 public/private/protected
核心用途 约束对象/函数结构、定义跨类的通用规范 作为父类模板,复用具体逻辑+约束子类行为
场景选择建议
  • 当需要跨类的通用规范 (如多个不相关的类需遵循同一套方法签名),用接口
    例:Movable 接口可被 CarPersonBike 等不相关的类实现。
  • 当需要复用具体逻辑+约束子类 (如多个相关的类共享部分属性/方法),用抽象类
    例:Animal 抽象类的 eat() 方法可被 DogCat 等相关子类复用,同时约束它们必须实现 shout()

【课堂小结】

  1. 接口是"契约",仅定义结构规范(属性类型、方法签名),无实现,可约束对象、函数、类的结构。
  2. 接口支持可选属性(?)、只读属性(readonly),可通过 extends 继承其他接口,同名接口会自动合并。
  3. 抽象类是"半实现模板",包含具体属性/方法和抽象方法(abstract),不能直接实例化,子类必须实现所有抽象方法。
  4. 一个类可实现多个接口,但只能继承一个抽象类;抽象类可实现接口并将接口方法的实现延迟到子类。
  5. 接口适用于跨类规范,抽象类适用于相关类的逻辑复用与约束。

【课后练习】

  1. 操作题:定义一个 Shape 接口,包含 width(宽度)、height(高度)属性,以及 calculateArea() 方法(返回面积,类型为 number)。创建 Rectangle(矩形)和 Triangle(三角形)类,实现 Shape 接口,分别计算面积(矩形:宽×高,三角形:宽×高÷2)。

  2. 操作题:定义一个 Flyable 抽象类,包含 altitude(高度)属性、fly() 抽象方法、land() 具体方法(输出"降落")。创建 BirdPlane 类继承 Flyable,实现 fly() 方法(分别输出"鸟在飞翔"和"飞机在飞行")。

  3. 分析题:以下代码是否正确?为什么?

    typescript 复制代码
    interface Runable {
      speed: number;
      run(): void;
    }
    class Student implements Runable {
      speed: number = 5;
      run(): string {  // 错误:返回值类型与接口不一致(接口要求void)
        return `以${this.speed}m/s跑步`;
      }
    }
  4. 思考题:如果需要让一个类同时遵循"可移动"和"可发声"两个规范,应该用接口还是抽象类?如何实现?

【下节预告】

第十节将学习泛型(generic),这是ArkTS实现"代码复用与类型安全"的关键机制。我们将掌握:

  • 泛型的定义语法:如何通过 <T> 定义通用类型参数
  • 泛型的核心用途:创建可复用的"类型无关"组件(如通用数组、通用函数)
  • 泛型约束:如何通过接口限制泛型的类型范围
  • 泛型的高级应用:泛型类、泛型接口的使用场景

通过泛型,我们能写出"一份代码适配多种类型"的灵活程序,同时保持类型检查的严格性,避免类型转换错误。

相关推荐
爱笑的眼睛113 小时前
鸿蒙应用开发:华为静默登录解决方案
华为·harmonyos
纯爱掌门人3 小时前
鸿蒙状态管理V2实战:从零构建MVVM架构的应用
前端·harmonyos
白鹿第一帅4 小时前
【案例实战】鸿蒙元服务开发实战:从云原生到移动端,包大小压缩 96% 启动提速 75% 的轻量化设计
harmonyos·白鹿第一帅·鸿蒙元服务·csdn成都站·鸿蒙开放能力·鸿蒙学习之路·鸿蒙元服务框架
爱笑的眼睛114 小时前
深入理解HarmonyOS中NavDestination导航目标页的生命周期
华为·harmonyos
白鹿第一帅5 小时前
【参赛心得】鸿蒙三方库适配实战:从 Hadoop 生态到鸿蒙生态,企业级项目集成的 6 个最佳实践
harmonyos·白鹿第一帅·鸿蒙三方库·csdn成都站·鸿蒙开放能力·鸿蒙学习之路·harmonyos创新赛
纯爱掌门人7 小时前
鸿蒙端云一体化云存储实战:手把手教你玩转文件上传下载
前端·harmonyos
用户498888174377 小时前
ArkTS 语言基础 第五节:流程控制
harmonyos
星释8 小时前
鸿蒙Flutter三方库适配指南-04.使用MacOS搭建开发环境
flutter·macos·harmonyos
ifeng09189 小时前
HarmonyOS分布式媒体播放器——跨设备音视频无缝流转
分布式·音视频·harmonyos