TypeScript 类与接口:面向对象编程
这一篇深入 TypeScript 的类和接口系统:从类的特性、抽象类,
到接口的定义和使用,再到类与接口的区别和选择。
类的基础
类的定义
1class User {
2 name: string;
3 age: number;
4
5 constructor(name: string, age: number) {
6 this.name = name;
7 this.age = age;
8 }
9
10 greet(): string {
11 return `Hello, I'm ${this.name}`;
12 }
13}
14
15const user = new User('John', 30);
16console.log(user.greet()); // "Hello, I'm John"
类的访问修饰符
TypeScript 提供了三种访问修饰符:
1class User {
2 public name: string; // 公开的(默认)
3 private age: number; // 私有的
4 protected email: string; // 受保护的
5
6 constructor(name: string, age: number, email: string) {
7 this.name = name;
8 this.age = age;
9 this.email = email;
10 }
11
12 public getAge(): number {
13 return this.age; // 类内部可以访问 private 成员
14 }
15}
16
17class Admin extends User {
18 constructor(name: string, age: number, email: string) {
19 super(name, age, email);
20 // this.age; // 错误:不能访问父类的 private 成员
21 // this.email; // 正确:可以访问父类的 protected 成员
22 }
23}
24
25const user = new User('John', 30, 'john@example.com');
26user.name; // 正确:public 成员可以访问
27// user.age; // 错误:private 成员不能从外部访问
28// user.email; // 错误:protected 成员不能从外部访问
readonly 修饰符
1class Point {
2 readonly x: number;
3 readonly y: number;
4
5 constructor(x: number, y: number) {
6 this.x = x;
7 this.y = y;
8 }
9
10 // 不能在方法中修改 readonly 属性
11 // move(dx: number, dy: number) {
12 // this.x += dx; // 错误:Cannot assign to 'x' because it is a read-only property
13 // }
14}
15
16const point = new Point(1, 2);
17// point.x = 3; // 错误:Cannot assign to 'x' because it is a read-only property
参数属性
TypeScript 允许在构造函数参数中直接声明属性:
1class User {
2 constructor(
3 public name: string,
4 private age: number,
5 protected email: string
6 ) {
7 // 不需要手动赋值,TypeScript 会自动创建属性
8 }
9}
10
11// 等价于
12class User2 {
13 public name: string;
14 private age: number;
15 protected email: string;
16
17 constructor(name: string, age: number, email: string) {
18 this.name = name;
19 this.age = age;
20 this.email = email;
21 }
22}
静态成员
1class MathUtils {
2 static PI = 3.14159;
3
4 static add(a: number, b: number): number {
5 return a + b;
6 }
7
8 static subtract(a: number, b: number): number {
9 return a - b;
10 }
11}
12
13// 通过类名访问静态成员
14console.log(MathUtils.PI); // 3.14159
15console.log(MathUtils.add(1, 2)); // 3
16
17// 不能通过实例访问静态成员
18const utils = new MathUtils();
19// utils.PI; // 错误:Property 'PI' does not exist on type 'MathUtils'
静态块
1class Database {
2 static connection: Connection;
3
4 static {
5 // 静态初始化块(ES2022)
6 this.connection = new Connection();
7 this.connection.connect();
8 }
9}
类的继承
基础继承
1class Animal {
2 name: string;
3
4 constructor(name: string) {
5 this.name = name;
6 }
7
8 move(distance: number = 0): void {
9 console.log(`${this.name} moved ${distance}m`);
10 }
11}
12
13class Dog extends Animal {
14 breed: string;
15
16 constructor(name: string, breed: string) {
17 super(name); // 必须调用 super()
18 this.breed = breed;
19 }
20
21 bark(): void {
22 console.log('Woof! Woof!');
23 }
24
25 // 重写父类方法
26 move(distance: number = 5): void {
27 console.log('Running...');
28 super.move(distance); // 调用父类方法
29 }
30}
31
32const dog = new Dog('Buddy', 'Golden Retriever');
33dog.move(10); // "Running..." "Buddy moved 10m"
34dog.bark(); // "Woof! Woof!"
方法重写
1class Base {
2 greet(): void {
3 console.log('Hello from Base');
4 }
5}
6
7class Derived extends Base {
8 // 重写方法时,参数类型必须兼容
9 greet(name?: string): void {
10 if (name) {
11 console.log(`Hello, ${name}`);
12 } else {
13 super.greet();
14 }
15 }
16}
17
18const derived = new Derived();
19derived.greet(); // "Hello from Base"
20derived.greet('John'); // "Hello, John"
访问修饰符在继承中的行为
1class Base {
2 public publicProp = 'public';
3 private privateProp = 'private';
4 protected protectedProp = 'protected';
5}
6
7class Derived extends Base {
8 showProtected(): void {
9 console.log(this.protectedProp); // 可以访问 protected 成员
10 // console.log(this.privateProp); // 错误:不能访问 private 成员
11 }
12}
13
14const derived = new Derived();
15console.log(derived.publicProp); // 可以访问 public 成员
16// console.log(derived.protectedProp); // 错误:不能从外部访问 protected 成员
17// console.log(derived.privateProp); // 错误:不能访问 private 成员
抽象类
抽象类不能直接实例化,只能被继承。
抽象类定义
1abstract class Animal {
2 name: string;
3
4 constructor(name: string) {
5 this.name = name;
6 }
7
8 // 抽象方法:必须在子类中实现
9 abstract makeSound(): void;
10
11 // 普通方法:可以有实现
12 move(): void {
13 console.log(`${this.name} is moving`);
14 }
15}
16
17// const animal = new Animal('Animal'); // 错误:不能实例化抽象类
18
19class Dog extends Animal {
20 makeSound(): void {
21 console.log('Woof!');
22 }
23}
24
25class Cat extends Animal {
26 makeSound(): void {
27 console.log('Meow!');
28 }
29}
30
31const dog = new Dog('Buddy');
32dog.makeSound(); // "Woof!"
33dog.move(); // "Buddy is moving"
抽象属性
1abstract class Shape {
2 abstract area: number; // 抽象属性
3
4 abstract calculateArea(): number; // 抽象方法
5
6 displayArea(): void {
7 console.log(`Area: ${this.area}`);
8 }
9}
10
11class Circle extends Shape {
12 radius: number;
13
14 constructor(radius: number) {
15 super();
16 this.radius = radius;
17 }
18
19 get area(): number {
20 return this.calculateArea();
21 }
22
23 calculateArea(): number {
24 return Math.PI * this.radius * this.radius;
25 }
26}
接口(Interfaces)
接口定义
1interface User {
2 name: string;
3 age: number;
4 email?: string; // 可选属性
5}
6
7const user: User = {
8 name: 'John',
9 age: 30
10 // email 是可选的,可以不提供
11};
只读属性
1interface Point {
2 readonly x: number;
3 readonly y: number;
4}
5
6const point: Point = { x: 1, y: 2 };
7// point.x = 3; // 错误:Cannot assign to 'x' because it is a read-only property
函数类型接口
1interface SearchFunc {
2 (source: string, subString: string): boolean;
3}
4
5const mySearch: SearchFunc = function(source: string, subString: string): boolean {
6 return source.indexOf(subString) > -1;
7};
8
9// 参数名可以不同
10const mySearch2: SearchFunc = function(src: string, sub: string): boolean {
11 return src.indexOf(sub) > -1;
12};
索引签名
1interface StringArray {
2 [index: number]: string;
3}
4
5const myArray: StringArray = ['hello', 'world'];
6const first = myArray[0]; // first 的类型是 string
7
8// 字符串索引签名
9interface Dictionary {
10 [key: string]: string | number;
11}
12
13const dict: Dictionary = {
14 name: 'John',
15 age: 30
16};
接口继承
1interface Shape {
2 color: string;
3}
4
5interface Square extends Shape {
6 sideLength: number;
7}
8
9const square: Square = {
10 color: 'red',
11 sideLength: 10
12};
13
14// 接口可以继承多个接口
15interface PenStroke {
16 penWidth: number;
17}
18
19interface Square2 extends Shape, PenStroke {
20 sideLength: number;
21}
接口合并(Declaration Merging)
1interface Window {
2 title: string;
3}
4
5interface Window {
6 version: string;
7}
8
9// 两个声明会合并
10const window: Window = {
11 title: 'My App',
12 version: '1.0.0'
13};
类实现接口
实现单个接口
1interface Flyable {
2 fly(): void;
3}
4
5class Bird implements Flyable {
6 fly(): void {
7 console.log('Flying...');
8 }
9}
10
11class Airplane implements Flyable {
12 fly(): void {
13 console.log('Flying at high speed...');
14 }
15}
实现多个接口
1interface Flyable {
2 fly(): void;
3}
4
5interface Swimmable {
6 swim(): void;
7}
8
9class Duck implements Flyable, Swimmable {
10 fly(): void {
11 console.log('Flying...');
12 }
13
14 swim(): void {
15 console.log('Swimming...');
16 }
17}
接口继承类
1class Control {
2 private state: any;
3}
4
5interface SelectableControl extends Control {
6 select(): void;
7}
8
9// 实现接口的类必须继承 Control
10class Button extends Control implements SelectableControl {
11 select(): void {
12 console.log('Selected');
13 }
14}
类与接口的区别
类型层面
1// 接口:编译后不存在,只用于类型检查
2interface IUser {
3 name: string;
4}
5
6// 类:编译后存在,既是类型又是值
7class User {
8 name: string;
9}
10
11// 接口只能用于类型注解
12const user1: IUser = { name: 'John' };
13
14// 类可以用于类型注解和实例化
15const user2: User = new User();
16const user3: User = { name: 'John' }; // 也可以用于对象字面量
实现层面
1// 接口:不能有实现
2interface ICalculator {
3 add(a: number, b: number): number;
4 // add(a: number, b: number): number { return a + b; } // 错误:接口不能有实现
5}
6
7// 类:可以有实现
8class Calculator implements ICalculator {
9 add(a: number, b: number): number {
10 return a + b;
11 }
12}
继承和扩展
1// 接口:支持声明合并
2interface Config {
3 api: string;
4}
5
6interface Config {
7 timeout: number;
8}
9// 两个声明会合并
10
11// 类:不支持声明合并
12class Config {
13 api: string;
14}
15
16// class Config { // 错误:重复声明
17// timeout: number;
18// }
选择建议
使用接口当:
- 只需要定义对象的形状
- 需要声明合并
- 需要定义函数类型、索引签名等
- 不需要实现细节
使用类当:
- 需要实例化
- 需要实现逻辑
- 需要继承
- 需要访问修饰符、静态成员等
实际应用场景
依赖注入模式
1interface Logger {
2 log(message: string): void;
3}
4
5class ConsoleLogger implements Logger {
6 log(message: string): void {
7 console.log(message);
8 }
9}
10
11class FileLogger implements Logger {
12 log(message: string): void {
13 // 写入文件
14 }
15}
16
17class Service {
18 constructor(private logger: Logger) {}
19
20 doSomething(): void {
21 this.logger.log('Doing something...');
22 }
23}
24
25// 可以轻松切换不同的实现
26const service1 = new Service(new ConsoleLogger());
27const service2 = new Service(new FileLogger());
策略模式
1interface PaymentStrategy {
2 pay(amount: number): void;
3}
4
5class CreditCardPayment implements PaymentStrategy {
6 pay(amount: number): void {
7 console.log(`Paying ${amount} with credit card`);
8 }
9}
10
11class PayPalPayment implements PaymentStrategy {
12 pay(amount: number): void {
13 console.log(`Paying ${amount} with PayPal`);
14 }
15}
16
17class PaymentProcessor {
18 constructor(private strategy: PaymentStrategy) {}
19
20 processPayment(amount: number): void {
21 this.strategy.pay(amount);
22 }
23
24 setStrategy(strategy: PaymentStrategy): void {
25 this.strategy = strategy;
26 }
27}
28
29const processor = new PaymentProcessor(new CreditCardPayment());
30processor.processPayment(100);
31processor.setStrategy(new PayPalPayment());
32processor.processPayment(200);
工厂模式
1interface Product {
2 name: string;
3 price: number;
4}
5
6class Book implements Product {
7 constructor(
8 public name: string,
9 public price: number
10 ) {}
11}
12
13class Electronics implements Product {
14 constructor(
15 public name: string,
16 public price: number
17 ) {}
18}
19
20class ProductFactory {
21 static create(type: 'book' | 'electronics', name: string, price: number): Product {
22 switch (type) {
23 case 'book':
24 return new Book(name, price);
25 case 'electronics':
26 return new Electronics(name, price);
27 }
28 }
29}
30
31const book = ProductFactory.create('book', 'TypeScript Guide', 29.99);
32const laptop = ProductFactory.create('electronics', 'Laptop', 999.99);
单例模式
1class Database {
2 private static instance: Database;
3
4 private constructor() {
5 // 私有构造函数,防止外部实例化
6 }
7
8 static getInstance(): Database {
9 if (!Database.instance) {
10 Database.instance = new Database();
11 }
12 return Database.instance;
13 }
14
15 connect(): void {
16 console.log('Connected to database');
17 }
18}
19
20// 只能通过 getInstance 获取实例
21const db1 = Database.getInstance();
22const db2 = Database.getInstance();
23console.log(db1 === db2); // true
小结
TypeScript 的类和接口系统提供了完整的面向对象编程支持:
- 类的特性:访问修饰符、readonly、参数属性、静态成员
- 类的继承:extends、super、方法重写、访问修饰符在继承中的行为
- 抽象类:不能实例化,用于定义抽象方法和属性
- 接口:定义对象形状、函数类型、索引签名、接口继承和合并
- 类实现接口:implements 关键字,实现单个或多个接口
- 类与接口的区别:类型层面、实现层面、继承和扩展、选择建议
理解这些特性,可以:
- 写出更结构化的代码
- 实现常见的设计模式
- 在需要时选择合适的抽象方式(类 vs 接口)
- 利用面向对象编程的优势组织代码
在实际开发中,应该:
- 优先使用接口定义契约,类实现具体逻辑
- 使用抽象类定义公共行为和抽象方法
- 合理使用访问修饰符控制封装性
- 利用接口的声明合并特性扩展类型定义