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 成员不能从外部访问
 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}
 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}
 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 接口)
  • 利用面向对象编程的优势组织代码

在实际开发中,应该:

  • 优先使用接口定义契约,类实现具体逻辑
  • 使用抽象类定义公共行为和抽象方法
  • 合理使用访问修饰符控制封装性
  • 利用接口的声明合并特性扩展类型定义