目录

第六章:类

本章概述

类是面向对象编程的核心概念。TypeScript 扩展了 JavaScript 的类,添加了类型注解、访问修饰符、抽象类等特性。本章将深入讲解 TypeScript 类的完整功能。

6.1 类基础

6.1.1 基本类定义

class Person {
  // 属性声明
  name: string;
  age: number;
 
  // 构造函数
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
 
  // 方法
  greet(): string {
    return `Hello, my name is ${this.name}`;
  }
 
  // 获取年龄
  getAge(): number {
    return this.age;
  }
}
 
// 创建实例
const alice = new Person("Alice", 25);
console.log(alice.greet());  // "Hello, my name is Alice"

6.1.2 属性简写语法

// 使用 public 修饰符简化属性声明
class Person2 {
  constructor(
    public name: string,
    public age: number,
    private id: string
  ) {}
 
  greet(): string {
    return `Hello, my name is ${this.name}`;
  }
}
 
const bob = new Person2("Bob", 30, "user-123");
console.log(bob.name);   // "Bob"
// console.log(bob.id);  // Error: 私有属性

6.2 访问修饰符

6.2.1 public(默认)

class Animal {
  public name: string;  // 默认就是 public
 
  constructor(name: string) {
    this.name = name;
  }
 
  public move(distance: number): void {
    console.log(`${this.name} moved ${distance}m`);
  }
}
 
const dog = new Animal("Dog");
console.log(dog.name);  // OK
 dog.move(10);          // OK

6.2.2 private

class BankAccount {
  private balance: number = 0;
  private readonly accountId: string;
 
  constructor(accountId: string, initialBalance: number) {
    this.accountId = accountId;
    this.balance = initialBalance;
  }
 
  deposit(amount: number): void {
    if (amount > 0) {
      this.balance += amount;
    }
  }
 
  withdraw(amount: number): boolean {
    if (amount > 0 && this.balance >= amount) {
      this.balance -= amount;
      return true;
    }
    return false;
  }
 
  getBalance(): number {
    return this.balance;
  }
}
 
const account = new BankAccount("ACC-001", 1000);
account.deposit(500);
console.log(account.getBalance());  // 1500
// console.log(account.balance);     // Error: 私有属性
// account.balance = 1000000;        // Error

6.2.3 protected

class Vehicle {
  protected brand: string;
  protected speed: number = 0;
 
  constructor(brand: string) {
    this.brand = brand;
  }
 
  protected accelerate(amount: number): void {
    this.speed += amount;
  }
 
  getSpeed(): number {
    return this.speed;
  }
}
 
class Car extends Vehicle {
  private model: string;
 
  constructor(brand: string, model: string) {
    super(brand);
    this.model = model;
  }
 
  speedUp(): void {
    // 可以访问父类的 protected 成员
    this.accelerate(10);
    console.log(`${this.brand} ${this.model} speeding up to ${this.speed}km/h`);
  }
}
 
const car = new Car("Toyota", "Camry");
car.speedUp();  // OK
// car.accelerate(20);  // Error: 受保护的方法
// car.brand;           // Error: 受保护的属性

6.2.4 readonly

class Employee {
  readonly id: string;
  name: string;
  readonly hireDate: Date;
 
  constructor(id: string, name: string) {
    this.id = id;
    this.name = name;
    this.hireDate = new Date();
  }
 
  // 不能在方法中修改 readonly 属性
  // updateId(newId: string): void {
  //   this.id = newId;  // Error
  // }
}
 
const emp = new Employee("E001", "Alice");
console.log(emp.id);
// emp.id = "E002";  // Error

6.3 继承

6.3.1 extends 关键字

class Animal {
  protected name: string;
 
  constructor(name: string) {
    this.name = name;
  }
 
  move(distance: number = 0): void {
    console.log(`${this.name} moved ${distance}m`);
  }
 
  makeSound(): void {
    console.log("Some sound");
  }
}
 
class Dog extends Animal {
  private breed: string;
 
  constructor(name: string, breed: string) {
    super(name);  // 必须调用父类构造函数
    this.breed = breed;
  }
 
  // 重写方法
  makeSound(): void {
    console.log("Woof! Woof!");
  }
 
  fetch(): void {
    console.log(`${this.name} is fetching the ball`);
  }
 
  getBreed(): string {
    return this.breed;
  }
}
 
const dog = new Dog("Buddy", "Golden Retriever");
dog.move(10);      // 继承的方法
dog.makeSound();   // 重写的方法
dog.fetch();       // 子类的方法

6.3.2 super 关键字

class Rectangle {
  constructor(
    protected width: number,
    protected height: number
  ) {}
 
  getArea(): number {
    return this.width * this.height;
  }
 
  describe(): string {
    return `Rectangle: ${this.width}x${this.height}`;
  }
}
 
class Square extends Rectangle {
  constructor(side: number) {
    super(side, side);  // 调用父类构造函数
  }
 
  describe(): string {
    // 调用父类方法
    const baseDescription = super.describe();
    return `${baseDescription} (Square)`;
  }
}
 
const square = new Square(5);
console.log(square.getArea());    // 25
console.log(square.describe());   // "Rectangle: 5x5 (Square)"

6.3.3 方法重写与重载

class Person {
  constructor(protected name: string) {}
 
  introduce(): string {
    return `I am ${this.name}`;
  }
}
 
class Student extends Person {
  constructor(
    name: string,
    private grade: number
  ) {
    super(name);
  }
 
  // 方法重写
  introduce(): string {
    const base = super.introduce();
    return `${base}, a student in grade ${this.grade}`;
  }
}
 
// 方法重载
class Calculator {
  add(a: number, b: number): number;
  add(a: string, b: string): string;
  add(a: any, b: any): any {
    if (typeof a === "number" && typeof b === "number") {
      return a + b;
    }
    return String(a) + String(b);
  }
}
 
const calc = new Calculator();
console.log(calc.add(1, 2));        // 3
console.log(calc.add("a", "b"));    // "ab"

6.4 抽象类

6.4.1 abstract 关键字

// 抽象类不能实例化
abstract class Shape {
  protected color: string;
 
  constructor(color: string) {
    this.color = color;
  }
 
  // 抽象方法必须在子类中实现
  abstract getArea(): number;
  abstract getPerimeter(): number;
 
  // 普通方法
  describe(): string {
    return `A ${this.color} shape`;
  }
}
 
// 具体类必须实现所有抽象方法
class Circle extends Shape {
  constructor(
    color: string,
    private radius: number
  ) {
    super(color);
  }
 
  getArea(): number {
    return Math.PI * this.radius ** 2;
  }
 
  getPerimeter(): number {
    return 2 * Math.PI * this.radius;
  }
}
 
class Rectangle extends Shape {
  constructor(
    color: string,
    private width: number,
    private height: number
  ) {
    super(color);
  }
 
  getArea(): number {
    return this.width * this.height;
  }
 
  getPerimeter(): number {
    return 2 * (this.width + this.height);
  }
}
 
// 使用
const shapes: Shape[] = [
  new Circle("red", 5),
  new Rectangle("blue", 4, 6)
];
 
shapes.forEach(shape => {
  console.log(`${shape.describe()}: Area=${shape.getArea().toFixed(2)}`);
});
 
// 抽象类不能实例化
// const shape = new Shape("green");  // Error

6.5 存取器(Getters 和 Setters)

6.5.1 基本使用

class Employee {
  private _name: string;
  private _salary: number;
 
  constructor(name: string, salary: number) {
    this._name = name;
    this._salary = salary;
  }
 
  // getter
  get name(): string {
    return this._name;
  }
 
  // setter
  set name(value: string) {
    if (value.length < 2) {
      throw new Error("Name must be at least 2 characters");
    }
    this._name = value;
  }
 
  get salary(): number {
    return this._salary;
  }
 
  // 只读属性(只有 getter)
  get annualSalary(): number {
    return this._salary * 12;
  }
}
 
const emp = new Employee("Alice", 5000);
console.log(emp.name);         // "Alice"
emp.name = "Bob";              // 使用 setter
console.log(emp.annualSalary); // 60000
// emp.salary = 6000;          // Error: 没有 setter

6.5.2 计算属性

class Temperature {
  private _celsius: number;
 
  constructor(celsius: number) {
    this._celsius = celsius;
  }
 
  get celsius(): number {
    return this._celsius;
  }
 
  set celsius(value: number) {
    this._celsius = value;
  }
 
  get fahrenheit(): number {
    return (this._celsius * 9 / 5) + 32;
  }
 
  set fahrenheit(value: number) {
    this._celsius = (value - 32) * 5 / 9;
  }
 
  get kelvin(): number {
    return this._celsius + 273.15;
  }
}
 
const temp = new Temperature(25);
console.log(temp.fahrenheit);  // 77
console.log(temp.kelvin);      // 298.15
 
temp.fahrenheit = 100;
console.log(temp.celsius);     // 37.777...

6.6 静态成员

6.6.1 静态属性和方法

class MathUtils {
  // 静态属性
  static readonly PI: number = 3.14159;
  static readonly E: number = 2.71828;
 
  // 静态方法
  static circleArea(radius: number): number {
    return this.PI * radius * radius;
  }
 
  static max(a: number, b: number): number {
    return a > b ? a : b;
  }
 
  // 静态工厂方法
  static createRandom(): number {
    return Math.random();
  }
}
 
// 无需实例化即可使用
console.log(MathUtils.PI);
console.log(MathUtils.circleArea(5));
console.log(MathUtils.max(10, 20));
 
// 不能通过实例访问
// const utils = new MathUtils();
// utils.PI;  // Error

6.6.2 单例模式

class Database {
  private static instance: Database;
  private connection: string;
 
  private constructor(connectionString: string) {
    this.connection = connectionString;
  }
 
  static getInstance(connectionString: string): Database {
    if (!Database.instance) {
      Database.instance = new Database(connectionString);
    }
    return Database.instance;
  }
 
  query(sql: string): any {
    console.log(`Executing: ${sql}`);
    return [];
  }
}
 
// 使用
const db1 = Database.getInstance("mysql://localhost");
const db2 = Database.getInstance("mysql://remote");  // 返回同一个实例
console.log(db1 === db2);  // true

6.7 类与接口

6.7.1 类实现接口

interface Printable {
  print(): void;
}
 
interface Loggable {
  log(): void;
}
 
// 类可以实现多个接口
class Document implements Printable, Loggable {
  constructor(private content: string) {}
 
  print(): void {
    console.log("Printing:", this.content);
  }
 
  log(): void {
    console.log("Logging document content");
  }
}

6.7.2 接口继承类

class Point {
  x: number;
  y: number;
 
  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}
 
// 接口继承类
interface Point3D extends Point {
  z: number;
}
 
const point3d: Point3D = {
  x: 1,
  y: 2,
  z: 3
};

6.8 高级类特性

6.8.1 泛型类

class GenericStack<T> {
  private items: T[] = [];
 
  push(item: T): void {
    this.items.push(item);
  }
 
  pop(): T | undefined {
    return this.items.pop();
  }
 
  peek(): T | undefined {
    return this.items[this.items.length - 1];
  }
 
  isEmpty(): boolean {
    return this.items.length === 0;
  }
 
  size(): number {
    return this.items.length;
  }
}
 
// 使用
const numberStack = new GenericStack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop());  // 2
 
const stringStack = new GenericStack<string>();
stringStack.push("hello");
stringStack.push("world");
console.log(stringStack.pop());  // "world"

6.8.2 抽象属性

abstract class Component {
  abstract id: string;  // 抽象属性
  abstract render(): void;
 
  mount(): void {
    console.log(`Mounting component ${this.id}`);
    this.render();
  }
}
 
class ButtonComponent extends Component {
  id: string = "button-" + Math.random().toString(36);
 
  render(): void {
    console.log("Rendering button");
  }
}

6.9 本章小结

本章我们学习了:

1. 类基础 - 属性声明、构造函数、方法

2. 访问修饰符 - public、private、protected、readonly

3. 继承 - extends、super、方法重写

4. 抽象类 - abstract、抽象方法

5. 存取器 - getter 和 setter

6. 静态成员 - 静态属性、静态方法、单例模式

7. 类与接口 - 类实现接口、接口继承类

8. 泛型类 - 类型参数化

6.10 练习题

练习 1:实现银行账户类

设计一个 BankAccount 类,要求:

  1. 私有属性:balance(余额)、accountNumber(账号)、ownerName(所有者)
  2. 公共方法:deposit(存款)、withdraw(取款)、getBalance(查询余额)
  3. 取款时检查余额是否充足
  4. 使用 getter 获取账户信息

参考答案

class BankAccount {
  private _balance: number = 0;
  private readonly _accountNumber: string;
  private readonly _ownerName: string;
  private _transactionHistory: string[] = [];
 
  constructor(accountNumber: string, ownerName: string, initialBalance: number = 0) {
    this._accountNumber = accountNumber;
    this._ownerName = ownerName;
    this._balance = initialBalance;
    this._logTransaction("ACCOUNT_OPENED", initialBalance);
  }
 
  get accountNumber(): string {
    return this._accountNumber;
  }
 
  get ownerName(): string {
    return this._ownerName;
  }
 
  get balance(): number {
    return this._balance;
  }
 
  get transactionHistory(): readonly string[] {
    return [...this._transactionHistory];
  }
 
  deposit(amount: number): void {
    if (amount <= 0) {
      throw new Error("Deposit amount must be positive");
    }
    this._balance += amount;
    this._logTransaction("DEPOSIT", amount);
  }
 
  withdraw(amount: number): void {
    if (amount <= 0) {
      throw new Error("Withdrawal amount must be positive");
    }
    if (amount > this._balance) {
      throw new Error("Insufficient funds");
    }
    this._balance -= amount;
    this._logTransaction("WITHDRAWAL", amount);
  }
 
  transfer(amount: number, targetAccount: BankAccount): void {
    this.withdraw(amount);
    targetAccount.deposit(amount);
    this._logTransaction(`TRANSFER_TO_${targetAccount.accountNumber}`, amount);
  }
 
  private _logTransaction(type: string, amount: number): void {
    const timestamp = new Date().toISOString();
    this._transactionHistory.push(
      `[${timestamp}] ${type}: $${amount.toFixed(2)}, Balance: $${this._balance.toFixed(2)}`
    );
  }
}
 
// 使用
const account1 = new BankAccount("ACC-001", "Alice", 1000);
const account2 = new BankAccount("ACC-002", "Bob", 500);
 
account1.deposit(500);
account1.transfer(200, account2);
 
console.log(account1.balance);           // 1300
console.log(account2.balance);           // 700
console.log(account1.transactionHistory);

练习 2:实现图形类层次结构

设计一个图形类系统,要求:

  1. 抽象基类 Shape,包含 color 属性和抽象方法 getArea()、getPerimeter()
  2. 具体类:Circle、Rectangle、Triangle
  3. 每个类有自己的属性(radius、width/height、sides)
  4. 实现静态工厂方法

参考答案

abstract class Shape {
  constructor(protected color: string) {}
 
  abstract getArea(): number;
  abstract getPerimeter(): number;
  abstract describe(): string;
 
  getColor(): string {
    return this.color;
  }
 
  setColor(color: string): void {
    this.color = color;
  }
}
 
class Circle extends Shape {
  constructor(color: string, private radius: number) {
    super(color);
    if (radius <= 0) {
      throw new Error("Radius must be positive");
    }
  }
 
  getArea(): number {
    return Math.PI * this.radius ** 2;
  }
 
  getPerimeter(): number {
    return 2 * Math.PI * this.radius;
  }
 
  describe(): string {
    return `Circle(${this.radius}) - ${this.color}`;
  }
 
  getRadius(): number {
    return this.radius;
  }
 
  setRadius(radius: number): void {
    if (radius <= 0) {
      throw new Error("Radius must be positive");
    }
    this.radius = radius;
  }
}
 
class Rectangle extends Shape {
  constructor(
    color: string,
    private width: number,
    private height: number
  ) {
    super(color);
    if (width <= 0 || height <= 0) {
      throw new Error("Dimensions must be positive");
    }
  }
 
  getArea(): number {
    return this.width * this.height;
  }
 
  getPerimeter(): number {
    return 2 * (this.width + this.height);
  }
 
  describe(): string {
    return `Rectangle(${this.width}x${this.height}) - ${this.color}`;
  }
 
  isSquare(): boolean {
    return this.width === this.height;
  }
}
 
class Triangle extends Shape {
  constructor(
    color: string,
    private a: number,
    private b: number,
    private c: number
  ) {
    super(color);
    // 验证三角形不等式
    if (a + b <= c || a + c <= b || b + c <= a) {
      throw new Error("Invalid triangle sides");
    }
  }
 
  getArea(): number {
    // 海伦公式
    const s = this.getPerimeter() / 2;
    return Math.sqrt(s * (s - this.a) * (s - this.b) * (s - this.c));
  }
 
  getPerimeter(): number {
    return this.a + this.b + this.c;
  }
 
  describe(): string {
    return `Triangle(${this.a}, ${this.b}, ${this.c}) - ${this.color}`;
  }
}
 
// 形状工厂
class ShapeFactory {
  static createCircle(color: string, radius: number): Circle {
    return new Circle(color, radius);
  }
 
  static createRectangle(color: string, width: number, height: number): Rectangle {
    return new Rectangle(color, width, height);
  }
 
  static createTriangle(color: string, a: number, b: number, c: number): Triangle {
    return new Triangle(color, a, b, c);
  }
 
  static createRandomShape(): Shape {
    const shapes = ["circle", "rectangle", "triangle"];
    const randomShape = shapes[Math.floor(Math.random() * shapes.length)];
    const color = ["red", "blue", "green"][Math.floor(Math.random() * 3)];
 
    switch (randomShape) {
      case "circle":
        return new Circle(color, Math.random() * 10 + 1);
      case "rectangle":
        return new Rectangle(color, Math.random() * 10 + 1, Math.random() * 10 + 1);
      case "triangle":
        return new Triangle(color, 3, 4, 5);
      default:
        throw new Error("Unknown shape");
    }
  }
}
 
// 使用
const shapes: Shape[] = [
  ShapeFactory.createCircle("red", 5),
  ShapeFactory.createRectangle("blue", 4, 6),
  ShapeFactory.createTriangle("green", 3, 4, 5)
];
 
shapes.forEach(shape => {
  console.log(`${shape.describe()}`);
  console.log(`  Area: ${shape.getArea().toFixed(2)}`);
  console.log(`  Perimeter: ${shape.getPerimeter().toFixed(2)}`);
});

练习 3:实现泛型集合类

实现一个泛型的 Collection 类,要求:

  1. 支持添加、删除、查询元素
  2. 支持过滤、映射、归约操作
  3. 支持迭代器

参考答案

class Collection<T> implements Iterable<T> {
  private items: T[] = [];
 
  add(item: T): void {
    this.items.push(item);
  }
 
  remove(item: T): boolean {
    const index = this.items.indexOf(item);
    if (index > -1) {
      this.items.splice(index, 1);
      return true;
    }
    return false;
  }
 
  removeAt(index: number): T | undefined {
    if (index < 0 || index >= this.items.length) {
      return undefined;
    }
    return this.items.splice(index, 1)[0];
  }
 
  get(index: number): T | undefined {
    return this.items[index];
  }
 
  contains(item: T): boolean {
    return this.items.includes(item);
  }
 
  size(): number {
    return this.items.length;
  }
 
  isEmpty(): boolean {
    return this.items.length === 0;
  }
 
  clear(): void {
    this.items = [];
  }
 
  toArray(): T[] {
    return [...this.items];
  }
 
  // 高阶函数
  filter(predicate: (item: T) => boolean): Collection<T> {
    const result = new Collection<T>();
    for (const item of this.items) {
      if (predicate(item)) {
        result.add(item);
      }
    }
    return result;
  }
 
  map<U>(transform: (item: T) => U): Collection<U> {
    const result = new Collection<U>();
    for (const item of this.items) {
      result.add(transform(item));
    }
    return result;
  }
 
  reduce<U>(reducer: (acc: U, item: T) => U, initial: U): U {
    let accumulator = initial;
    for (const item of this.items) {
      accumulator = reducer(accumulator, item);
    }
    return accumulator;
  }
 
  find(predicate: (item: T) => boolean): T | undefined {
    return this.items.find(predicate);
  }
 
  every(predicate: (item: T) => boolean): boolean {
    return this.items.every(predicate);
  }
 
  some(predicate: (item: T) => boolean): boolean {
    return this.items.some(predicate);
  }
 
  // 实现迭代器
  [Symbol.iterator](): Iterator<T> {
    let index = 0;
    const items = this.items;
 
    return {
      next(): IteratorResult<T> {
        if (index < items.length) {
          return { value: items[index++], done: false };
        }
        return { value: undefined as any, done: true };
      }
    };
  }
 
  // 排序
  sort(compareFn?: (a: T, b: T) => number): Collection<T> {
    const result = new Collection<T>();
    result.items = [...this.items].sort(compareFn);
    return result;
  }
 
  // 反转
  reverse(): Collection<T> {
    const result = new Collection<T>();
    result.items = [...this.items].reverse();
    return result;
  }
}
 
// 使用
const numbers = new Collection<number>();
numbers.add(5);
numbers.add(2);
numbers.add(8);
numbers.add(1);
 
console.log([...numbers]);  // [5, 2, 8, 1]
 
const evens = numbers.filter(n => n % 2 === 0);
console.log([...evens]);  // [2, 8]
 
const doubled = numbers.map(n => n * 2);
console.log([...doubled]);  // [10, 4, 16, 2]
 
const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(sum);  // 16
 
const sorted = numbers.sort((a, b) => a - b);
console.log([...sorted]);  // [1, 2, 5, 8]

扩展阅读