Skip to content

TypeScript类与面向对象编程

TypeScript提供了全面的面向对象编程支持,包括类、接口、继承、多态等特性。本文将深入探讨TypeScript中的类与面向对象编程概念。

类定义

构造函数与属性

在TypeScript中,类的定义包括属性声明、构造函数和方法:

typescript
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} and I am ${this.age} years old.`;
  }
}

// 创建实例
const person = new Person('Alice', 30);
console.log(person.greet()); // "Hello, my name is Alice and I am 30 years old."

属性初始化的简写方式

typescript
class Person {
  // 参数属性 - 简化属性声明和初始化
  constructor(public name: string, public age: number) {
    // 不需要额外的赋值语句
  }
  
  greet(): string {
    return `Hello, my name is ${this.name} and I am ${this.age} years old.`;
  }
}

访问修饰符

TypeScript提供了三种访问修饰符来控制类成员的可见性:

typescript
class BankAccount {
  // public - 默认,可从任何地方访问
  public accountHolder: string;
  
  // private - 只能在类内部访问
  private balance: number;
  
  // protected - 只能在类内部和子类中访问
  protected accountNumber: string;
  
  constructor(holder: string, initialBalance: number) {
    this.accountHolder = holder;
    this.balance = initialBalance;
    this.accountNumber = this.generateAccountNumber();
  }
  
  // 私有方法
  private generateAccountNumber(): string {
    return Math.random().toString(36).substring(2, 18);
  }
  
  // 公共方法
  public deposit(amount: number): void {
    if (amount <= 0) {
      throw new Error('Deposit amount must be positive');
    }
    this.balance += amount;
  }
  
  public withdraw(amount: number): boolean {
    if (amount <= 0) {
      throw new Error('Withdrawal amount must be positive');
    }
    
    if (amount > this.balance) {
      return false; // 余额不足
    }
    
    this.balance -= amount;
    return true;
  }
  
  public getBalance(): number {
    return this.balance;
  }
}

const account = new BankAccount('John Doe', 1000);
account.deposit(500);
console.log(account.getBalance()); // 1500

// 错误:属性"balance"为私有属性,只能在类"BankAccount"中访问
// console.log(account.balance);

// 错误:属性"accountNumber"受保护,只能在类"BankAccount"及其子类中访问
// console.log(account.accountNumber);

静态成员与实例成员

类可以包含静态成员(属于类本身)和实例成员(属于类的实例):

typescript
class MathHelper {
  // 静态属性
  static readonly PI: number = 3.14159;
  
  // 实例属性
  precision: number;
  
  constructor(precision: number = 2) {
    this.precision = precision;
  }
  
  // 静态方法
  static square(x: number): number {
    return x * x;
  }
  
  static cube(x: number): number {
    return x * x * x;
  }
  
  // 实例方法
  round(value: number): number {
    const factor = Math.pow(10, this.precision);
    return Math.round(value * factor) / factor;
  }
}

// 使用静态成员
console.log(MathHelper.PI); // 3.14159
console.log(MathHelper.square(4)); // 16
console.log(MathHelper.cube(3)); // 27

// 使用实例成员
const helper = new MathHelper(3);
console.log(helper.round(MathHelper.PI)); // 3.142

抽象类与方法

抽象类是不能直接实例化的类,它们通常包含抽象方法,这些方法必须在子类中实现:

typescript
// 抽象类
abstract class Shape {
  // 普通属性
  color: string;
  
  constructor(color: string) {
    this.color = color;
  }
  
  // 抽象方法 - 没有实现,必须在子类中实现
  abstract calculateArea(): number;
  abstract calculatePerimeter(): number;
  
  // 普通方法
  getColor(): string {
    return this.color;
  }
}

// 具体类
class Circle extends Shape {
  radius: number;
  
  constructor(color: string, radius: number) {
    super(color);
    this.radius = radius;
  }
  
  // 实现抽象方法
  calculateArea(): number {
    return Math.PI * this.radius * this.radius;
  }
  
  calculatePerimeter(): number {
    return 2 * Math.PI * this.radius;
  }
}

class Rectangle extends Shape {
  width: number;
  height: number;
  
  constructor(color: string, width: number, height: number) {
    super(color);
    this.width = width;
    this.height = height;
  }
  
  calculateArea(): number {
    return this.width * this.height;
  }
  
  calculatePerimeter(): number {
    return 2 * (this.width + this.height);
  }
}

// 错误:无法创建抽象类的实例
// const shape = new Shape('red');

const circle = new Circle('blue', 5);
console.log(circle.calculateArea()); // 78.54...
console.log(circle.calculatePerimeter()); // 31.42...

const rectangle = new Rectangle('green', 4, 6);
console.log(rectangle.calculateArea()); // 24
console.log(rectangle.calculatePerimeter()); // 20

方法重载

TypeScript支持方法重载,允许一个方法根据不同的参数类型或数量有不同的行为:

typescript
class Calculator {
  // 重载签名
  add(a: number, b: number): number;
  add(a: string, b: string): string;
  add(a: Date, b: number): Date;
  
  // 实现
  add(a: number | string | Date, b: number | string): number | string | Date {
    if (typeof a === 'number' && typeof b === 'number') {
      return a + b;
    } else if (typeof a === 'string' && typeof b === 'string') {
      return a.concat(b);
    } else if (a instanceof Date && typeof b === 'number') {
      const date = new Date(a);
      date.setDate(date.getDate() + b);
      return date;
    }
    throw new Error('Invalid arguments');
  }
}

const calc = new Calculator();
console.log(calc.add(5, 3)); // 8
console.log(calc.add('Hello, ', 'World')); // "Hello, World"

const today = new Date();
const future = calc.add(today, 7); // 7天后的日期

类继承

继承机制与super关键字

TypeScript支持单继承,子类可以继承父类的属性和方法:

typescript
// 基类
class Animal {
  name: string;
  
  constructor(name: string) {
    this.name = name;
  }
  
  move(distance: number = 0): void {
    console.log(`${this.name} moved ${distance} meters.`);
  }
}

// 派生类
class Dog extends Animal {
  breed: string;
  
  constructor(name: string, breed: string) {
    // 调用父类构造函数
    super(name);
    this.breed = breed;
  }
  
  // 重写父类方法
  move(distance: number = 5): void {
    console.log(`${this.name} is running...`);
    // 调用父类方法
    super.move(distance);
  }
  
  bark(): void {
    console.log('Woof! Woof!');
  }
}

const dog = new Dog('Rex', 'German Shepherd');
dog.bark(); // "Woof! Woof!"
dog.move(10); // "Rex is running..." 然后 "Rex moved 10 meters."
console.log(dog.breed); // "German Shepherd"

方法重写与多态

子类可以重写父类的方法,实现多态行为:

typescript
class Animal {
  makeSound(): void {
    console.log('Some generic sound');
  }
  
  sleep(): void {
    console.log('Zzz...');
  }
}

class Dog extends Animal {
  // 重写方法
  makeSound(): void {
    console.log('Woof! Woof!');
  }
}

class Cat extends Animal {
  // 重写方法
  makeSound(): void {
    console.log('Meow!');
  }
}

// 多态
function makeAnimalSound(animal: Animal): void {
  animal.makeSound();
}

const dog = new Dog();
const cat = new Cat();

makeAnimalSound(dog); // "Woof! Woof!"
makeAnimalSound(cat); // "Meow!"

// 父类方法仍然可用
dog.sleep(); // "Zzz..."
cat.sleep(); // "Zzz..."

接口实现与多接口

类可以实现一个或多个接口,确保它们提供特定的方法和属性:

typescript
// 定义接口
interface Movable {
  move(distance: number): void;
}

interface Resizable {
  resize(factor: number): void;
}

// 实现单个接口
class Car implements Movable {
  position: number = 0;
  
  move(distance: number): void {
    this.position += distance;
    console.log(`Car moved to position ${this.position}`);
  }
}

// 实现多个接口
class Rectangle implements Movable, Resizable {
  x: number = 0;
  y: number = 0;
  width: number;
  height: number;
  
  constructor(width: number, height: number) {
    this.width = width;
    this.height = height;
  }
  
  move(distance: number): void {
    this.x += distance;
    this.y += distance;
    console.log(`Rectangle moved to position (${this.x}, ${this.y})`);
  }
  
  resize(factor: number): void {
    this.width *= factor;
    this.height *= factor;
    console.log(`Rectangle resized to ${this.width}x${this.height}`);
  }
}

const car = new Car();
car.move(10); // "Car moved to position 10"

const rect = new Rectangle(5, 10);
rect.move(5); // "Rectangle moved to position (5, 5)"
rect.resize(2); // "Rectangle resized to 10x20"

混入模式(Mixins)

混入是一种代码复用模式,允许将多个类的功能组合到一个类中:

typescript
// 混入类 - 只包含要混入的功能
class Timestamped {
  timestamp: Date = new Date();
  
  getTimestamp(): Date {
    return this.timestamp;
  }
}

class Activatable {
  isActive: boolean = false;
  
  activate(): void {
    this.isActive = true;
  }
  
  deactivate(): void {
    this.isActive = false;
  }
}

// 目标类
class User {
  name: string;
  
  constructor(name: string) {
    this.name = name;
  }
}

// 混入函数
function applyMixins(derivedCtor: any, baseCtors: any[]) {
  baseCtors.forEach(baseCtor => {
    Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
      if (name !== 'constructor') {
        derivedCtor.prototype[name] = baseCtor.prototype[name];
      }
    });
  });
}

// 接口声明混入的属性和方法
interface User extends Timestamped, Activatable {}

// 应用混入
applyMixins(User, [Timestamped, Activatable]);

// 使用混入后的类
const user = new User('John');
console.log(user.name); // "John"
console.log(user.getTimestamp()); // 当前日期时间
console.log(user.isActive); // false
user.activate();
console.log(user.isActive); // true

装饰器模式

TypeScript支持装饰器,这是一种特殊类型的声明,可以附加到类、方法、访问器、属性或参数上:

typescript
// 需要在tsconfig.json中启用 "experimentalDecorators": true

// 类装饰器
function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

// 方法装饰器
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  
  descriptor.value = function(...args: any[]) {
    console.log(`Calling ${propertyKey} with:`, args);
    const result = originalMethod.apply(this, args);
    console.log(`Result:`, result);
    return result;
  };
  
  return descriptor;
}

// 属性装饰器
function format(formatString: string) {
  return function(target: any, propertyKey: string) {
    let value: any;
    
    const getter = function() {
      return value;
    };
    
    const setter = function(newVal: any) {
      value = typeof newVal === 'number' 
        ? formatString.replace('{0}', newVal.toString())
        : newVal;
    };
    
    Object.defineProperty(target, propertyKey, {
      get: getter,
      set: setter,
      enumerable: true,
      configurable: true
    });
  };
}

// 参数装饰器
function required(target: any, propertyKey: string, parameterIndex: number) {
  const requiredParams: number[] = Reflect.getMetadata('required', target, propertyKey) || [];
  requiredParams.push(parameterIndex);
  Reflect.defineMetadata('required', requiredParams, target, propertyKey);
}

// 使用装饰器
@sealed
class Greeter {
  @format('Value: {0}')
  greeting: string;
  
  constructor(message: string) {
    this.greeting = message;
  }
  
  @log
  greet(@required name: string): string {
    return `${this.greeting}, ${name}!`;
  }
}

const greeter = new Greeter('Hello');
console.log(greeter.greeting); // "Value: Hello"
greeter.greet('World'); // 日志输出和 "Hello, World!"

高级特性

只读属性与参数属性

TypeScript允许将属性标记为只读,确保它们在初始化后不能被修改:

typescript
class Point {
  // 只读属性
  readonly x: number;
  readonly y: number;
  
  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
  
  // 错误:无法分配到"x",因为它是只读属性
  // moveX(value: number): void {
  //   this.x = value;
  // }
}

// 参数属性简写
class Circle {
  // 使用参数属性简化只读属性声明
  constructor(
    public readonly center: Point,
    public readonly radius: number
  ) {}
  
  calculateArea(): number {
    return Math.PI * this.radius * this.radius;
  }
}

const point = new Point(10, 20);
// 错误:无法分配到"x",因为它是只读属性
// point.x = 30;

const circle = new Circle(point, 15);
console.log(circle.calculateArea()); // 706.86...

访问器getter/setter

TypeScript支持属性访问器,允许你拦截属性的读取和写入操作:

typescript
class Person {
  private _age: number = 0;
  private _firstName: string = '';
  private _lastName: string = '';
  
  // age的getter和setter
  get age(): number {
    return this._age;
  }
  
  set age(value: number) {
    if (value < 0 || value > 120) {
      throw new Error('Age must be between 0 and 120');
    }
    this._age = value;
  }
  
  // firstName的getter和setter
  get firstName(): string {
    return this._firstName;
  }
  
  set firstName(value: string) {
    if (value.length === 0) {
      throw new Error('First name cannot be empty');
    }
    this._firstName = value;
  }
  
  // lastName的getter和setter
  get lastName(): string {
    return this._lastName;
  }
  
  set lastName(value: string) {
    if (value.length === 0) {
      throw new Error('Last name cannot be empty');
    }
    this._lastName = value;
  }
  
  // 只读计算属性
  get fullName(): string {
    return `${this._firstName} ${this._lastName}`;
  }
}

const person = new Person();
person.firstName = 'John';
person.lastName = 'Doe';
person.age = 30;

console.log(person.fullName); // "John Doe"

try {
  person.age = 150; // 抛出错误
} catch (error) {
  console.error(error.message); // "Age must be between 0 and 120"
}

索引签名

索引签名允许你定义具有动态属性名的类:

typescript
class Dictionary {
  // 索引签名
  [key: string]: string | number | undefined;
  
  // 固定属性
  readonly name: string;
  count: number = 0;
  
  constructor(name: string) {
    this.name = name;
  }
  
  add(key: string, value: string): void {
    this[key] = value;
    this.count++;
  }
  
  get(key: string): string | undefined {
    return this[key] as string | undefined;
  }
  
  remove(key: string): boolean {
    if (this[key] !== undefined) {
      delete this[key];
      this.count--;
      return true;
    }
    return false;
  }
}

const dict = new Dictionary('MyDictionary');
dict.add('apple', 'A fruit');
dict.add('car', 'A vehicle');

console.log(dict.get('apple')); // "A fruit"
console.log(dict.count); // 2

dict.remove('apple');
console.log(dict.get('apple')); // undefined
console.log(dict.count); // 1

this类型与方法链

this类型允许方法返回当前实例,实现方法链:

typescript
class Calculator {
  private value: number = 0;
  
  add(operand: number): this {
    this.value += operand;
    return this;
  }
  
  subtract(operand: number): this {
    this.value -= operand;
    return this;
  }
  
  multiply(operand: number): this {
    this.value *= operand;
    return this;
  }
  
  divide(operand: number): this {
    if (operand === 0) {
      throw new Error('Division by zero');
    }
    this.value /= operand;
    return this;
  }
  
  getValue(): number {
    return this.value;
  }
}

// 使用方法链
const result = new Calculator()
  .add(10)
  .multiply(2)
  .subtract(5)
  .divide(5)
  .getValue();

console.log(result); // 3

在子类中使用this类型

typescript
class ScientificCalculator extends Calculator {
  square(): this {
    const value = this.getValue();
    return this.multiply(value);
  }
  
  sin(): this {
    const value = this.getValue();
    // 替换当前值为其正弦值
    return this.add(Math.sin(value) - value);
  }
}

// 方法链,包括子类方法
const sciResult = new ScientificCalculator()
  .add(Math.PI / 2)
  .sin()
  .getValue();

console.log(sciResult); // 约为1(sin(π/2) = 1)

类型守卫与类型断言

类型守卫和类型断言可以帮助TypeScript更精确地推断类的类型:

typescript
// 基类和子类
class Animal {
  name: string;
  
  constructor(name: string) {
    this.name = name;
  }
  
  move(): void {
    console.log(`${this.name} is moving`);
  }
}

class Bird extends Animal {
  fly(): void {
    console.log(`${this.name} is flying`);
  }
}

class Fish extends Animal {
  swim(): void {
    console.log(`${this.name} is swimming`);
  }
}

// 类型守卫函数
function isBird(animal: Animal): animal is Bird {
  return (animal as Bird).fly !== undefined;
}

function isFish(animal: Animal): animal is Fish {
  return (animal as Fish).swim !== undefined;
}

// 使用类型守卫
function makeAnimalMove(animal: Animal): void {
  animal.move();
  
  if (isBird(animal)) {
    // 在这个块中,TypeScript知道animal是Bird类型
    animal.fly();
  } else if (isFish(animal)) {
    // 在这个块中,TypeScript知道animal是Fish类型
    animal.swim();
  }
}

// 使用instanceof作为类型守卫
function moveAnimal(animal: Animal): void {
  animal.move();
  
  if (animal instanceof Bird) {
    animal.fly();
  } else if (animal instanceof Fish) {
    animal.swim();
  }
}

// 类型断言
function tryToFly(animal: Animal): void {
  if (animal instanceof Bird) {
    // 不需要类型断言,TypeScript已经知道类型
    animal.fly();
  } else {
    // 使用类型断言(不安全,可能运行时错误)
    try {
      (animal as Bird).fly();
    } catch (error) {
      console.error(`${animal.name} cannot fly!`);
    }
  }
}

const bird = new Bird('Sparrow');
const fish = new Fish('Salmon');

makeAnimalMove(bird); // "Sparrow is moving" 然后 "Sparrow is flying"
makeAnimalMove(fish); // "Salmon is moving" 然后 "Salmon is swimming"

tryToFly(bird); // "Sparrow is flying"
tryToFly(fish); // "Salmon cannot fly!"

总结

TypeScript的类与面向对象编程特性提供了强大的工具来构建结构化、可维护的代码。通过使用类、继承、接口、访问修饰符和其他高级特性,你可以创建清晰的类型层次结构,确保代码的类型安全,并利用面向对象编程的所有优势。

无论你是构建小型应用还是大型企业系统,TypeScript的面向对象特性都能帮助你组织代码,减少错误,并提高开发效率。

用知识点燃技术的火山