TypeScript深度强化第三天

300 阅读23分钟

TypeScript 深度强化第三天:类与继承、装饰器、模块系统 🏗️

学会搭建大型"积木建筑" - 掌握 TypeScript 的面向对象编程核心

📖 今日学习目标

第三天我们将深入 TypeScript 的核心架构特性,这些是构建大型应用程序的基石:

  • 🏗️ 类与继承 - 面向对象编程的核心,代码复用和组织的利器
  • 📦 模块系统 - 代码组织与重用,构建可维护的项目结构
  • 装饰器 - 元编程的利器,横切关注点的优雅解决方案
  • 🔗 声明合并 - 类型系统的灵活性扩展
  • 🎯 枚举 - 常量管理的最佳实践

📚 目录

  1. TypeScript 类系统深度解析
  2. 模块系统与命名空间
  3. 装饰器与元编程
  4. 声明合并与类型扩展
  5. 枚举的艺术
  6. 混入(Mixins)模式
  7. 实战案例:企业级任务管理系统
  8. 学习总结

1. TypeScript 类系统深度解析 🏗️

1.1 为什么需要类?

在现代软件开发中,面向对象编程(OOP)已经成为主流的编程范式之一。TypeScript 的类系统继承了 JavaScript ES6+ 的 class 语法,并在此基础上添加了强大的类型检查、访问修饰符等特性,让我们能够构建更加健壮和可维护的应用程序。

面向对象编程的核心思想:

面向对象编程是一种程序设计思想,它将现实世界中的事物抽象为程序中的对象。每个对象都有自己的属性(数据)和方法(行为)。这种编程方式更接近人类的思维模式,让复杂的程序变得更容易理解和维护。

类的核心优势:

  • 📊 封装性(Encapsulation) - 将数据和操作数据的方法组合在一起,隐藏内部实现细节
  • 🔄 继承性(Inheritance) - 子类可以继承父类的属性和方法,实现代码复用
  • 🎭 多态性(Polymorphism) - 同一个接口可以有不同的实现方式
  • 🔒 访问控制 - 通过访问修饰符控制成员的可见性和访问权限

TypeScript 类系统的特殊优势:

  1. 编译时类型检查 - 在开发阶段就能发现类型错误
  2. 丰富的访问修饰符 - public、private、protected、readonly 等
  3. 接口实现 - 确保类遵循特定的契约
  4. 抽象类支持 - 为相关类提供共同的基础结构
  5. 装饰器支持 - 提供元编程能力

1.2 基础类定义与实例化

什么是类?

类(Class)是面向对象编程的核心概念,它是创建对象的模板或蓝图。就像建筑师用图纸来建造房子一样,我们用类来创建对象。类定义了对象应该具有的属性和方法,但它本身不是对象,只有通过实例化才能创建具体的对象。

类与对象的关系:

  • 类(Class):抽象的模板,定义了对象的结构和行为
  • 对象(Object):类的具体实例,拥有实际的数据和功能
  • 实例化(Instantiation):根据类创建对象的过程

类的核心组成要素:

  1. 属性(Properties):存储对象的状态数据
  2. 方法(Methods):定义对象的行为和功能
  3. 构造函数(Constructor):初始化新创建的对象
  4. 访问修饰符(Access Modifiers):控制成员的访问权限

让我们通过一个用户管理系统的例子来深入理解这些概念:

// 用户类 - 展示类的基本结构
class User {
  // 公共属性 - 外部可以直接访问和修改
  public username: string
  public email: string
  
  // 私有属性 - 只有类内部可以访问,外部无法直接访问
  private password: string
  
  // 只读属性 - 一旦设置就不能修改,保证数据的不变性
  readonly userId: number
  
  // 静态属性 - 属于整个类,而不是某个特定实例
  static totalUsers: number = 0
  
  // 构造函数 - 创建对象时自动调用,用于初始化对象
  constructor(username: string, email: string, password: string) {
    this.username = username
    this.email = email
    this.password = password
    this.userId = Date.now() // 使用时间戳作为唯一ID
    User.totalUsers++ // 每创建一个用户,总数加1
  }
  
  // 公共方法 - 外部可以调用的功能
  public getUserInfo(): string {
    return `用户:${this.username},邮箱:${this.email}`
  }
  
  // 私有方法 - 只在类内部使用,封装内部逻辑
  private validatePassword(inputPassword: string): boolean {
    return this.password === inputPassword
  }
  
  // 业务方法 - 组合使用公共和私有方法
  public login(password: string): boolean {
    if (this.validatePassword(password)) {
      console.log(`${this.username} 登录成功!`)
      return true
    }
    console.log(`密码错误!`)
    return false
  }
  
  // 静态方法 - 通过类名直接调用,不需要实例化
  static getTotalUsers(): number {
    return User.totalUsers
  }
}

类的使用示例:

理解了类的定义后,让我们看看如何使用这个类:

// 创建用户实例 - 实例化过程
const user1 = new User('张三', 'zhangsan@example.com', '123456')
const user2 = new User('李四', 'lisi@example.com', '654321')

// 访问公共属性
console.log(user1.username) // "张三"
console.log(user1.email)    // "zhangsan@example.com"

// 调用公共方法
console.log(user1.getUserInfo()) // "用户:张三,邮箱:zhangsan@example.com"

// 测试登录功能
user1.login('123456') // "张三 登录成功!"
user1.login('wrong')   // "密码错误!"

// 访问静态属性和方法
console.log(User.totalUsers)     // 2
console.log(User.getTotalUsers()) // 2

// 尝试访问私有属性(会报错)
// console.log(user1.password) // ❌ 编译错误:属性"password"为私有属性

关键理解点:

  1. 封装性的体现private 属性如 password 外部无法直接访问,保护了敏感数据的安全性
  2. 数据完整性readonly 属性如 userId 一旦设置就不能修改,确保数据的一致性
  3. 类级别数据static 属性如 totalUsers 属于整个类,所有实例共享同一份数据
  4. 方法的层次性:公共方法对外提供服务,私有方法封装内部逻辑

1.3 继承与多态深入理解

继承的本质和价值

继承(Inheritance)是面向对象编程的核心特性之一,它允许我们基于现有的类创建新的类。这就像生物学中的遗传一样,子类会"遗传"父类的特征,同时还可以发展出自己独特的特征。

继承解决的核心问题:

  1. 代码重复 - 避免在多个类中编写相同的代码
  2. 维护困难 - 当需要修改共同功能时,只需修改父类
  3. 扩展性差 - 新增相似功能的类时,可以基于现有类进行扩展
  4. 概念不清晰 - 通过继承层次表达对象间的关系

多态的深层含义

多态(Polymorphism)意味着"多种形态"。在编程中,它指的是同一个方法在不同的类中可以有不同的实现。这就像"运动"这个概念,鱼类的运动是游泳,鸟类的运动是飞翔,人类的运动是跑步,虽然都是运动,但实现方式完全不同。

让我们通过一个完整的员工管理系统来深入理解继承和多态:

// 员工基类 - 定义所有员工的共同特征
class Employee {
  // 受保护属性 - 子类可以访问,但外部不能直接访问
  protected name: string
  protected department: string
  protected baseSalary: number
  protected hireDate: Date
  
  constructor(name: string, department: string, baseSalary: number) {
    this.name = name
    this.department = department
    this.baseSalary = baseSalary
    this.hireDate = new Date()
  }
  
  // 基础工资计算方法 - 子类可以重写(多态的基础)
  public calculateSalary(): number {
    return this.baseSalary
  }
  
  // 获取员工基本信息
  public getInfo(): string {
    return `员工:${this.name},部门:${this.department}`
  }
  
  // 计算工作年限
  public getWorkYears(): number {
    const currentDate = new Date()
    return currentDate.getFullYear() - this.hireDate.getFullYear()
  }
}

现在让我们创建不同类型的员工类,展示继承和多态的威力:

// 销售员工类 - 继承自Employee,有自己特殊的工资计算方式
class SalesEmployee extends Employee {
  private salesAmount: number = 0
  private commissionRate: number
  
  constructor(name: string, baseSalary: number, commissionRate: number) {
    // 调用父类构造函数,继承父类的初始化逻辑
    super(name, '销售部', baseSalary)
    this.commissionRate = commissionRate
  }
  
  // 重写父类的工资计算方法(多态的体现)
  public calculateSalary(): number {
    const baseSalary = super.calculateSalary() // 调用父类方法
    const commission = this.salesAmount * this.commissionRate
    return baseSalary + commission
  }
  
  // 销售员工特有的方法
  public recordSale(amount: number): void {
    this.salesAmount += amount
    console.log(`${this.name} 新增销售业绩:${amount}元,总业绩:${this.salesAmount}元`)
  }
  
  // 获取销售业绩
  public getSalesAmount(): number {
    return this.salesAmount
  }
}

// 技术员工类 - 另一种继承实现
class TechEmployee extends Employee {
  private techLevel: number
  private projects: string[] = []
  
  constructor(name: string, baseSalary: number, techLevel: number) {
    super(name, '技术部', baseSalary)
    this.techLevel = techLevel
  }
  
  // 重写工资计算方法 - 基于技术等级
  public calculateSalary(): number {
    const baseSalary = super.calculateSalary()
    const techBonus = this.techLevel * 1000 // 每个技术等级1000元奖金
    const projectBonus = this.projects.length * 500 // 每个项目500元奖金
    return baseSalary + techBonus + projectBonus
  }
  
  // 技术员工特有的方法
  public assignProject(projectName: string): void {
    this.projects.push(projectName)
    console.log(`${this.name} 被分配到项目:${projectName}`)
  }
  
  public getProjects(): string[] {
    return [...this.projects] // 返回副本,保护内部数据
  }
}

// 管理员工类 - 展示更复杂的继承关系
class ManagerEmployee extends Employee {
  private teamMembers: Employee[] = []
  private managementBonus: number
  
  constructor(name: string, baseSalary: number, managementBonus: number) {
    super(name, '管理部', baseSalary)
    this.managementBonus = managementBonus
  }
  
  // 管理者的工资计算包含管理奖金和团队奖金
  public calculateSalary(): number {
    const baseSalary = super.calculateSalary()
    const teamBonus = this.teamMembers.length * 200 // 每个下属200元奖金
    return baseSalary + this.managementBonus + teamBonus
  }
  
  // 管理者特有的方法
  public addTeamMember(employee: Employee): void {
    this.teamMembers.push(employee)
    console.log(`${employee.getInfo()} 加入了 ${this.name} 的团队`)
  }
  
  public getTeamSize(): number {
    return this.teamMembers.length
  }
}

多态的实际应用演示:

// 创建不同类型的员工
const employees: Employee[] = [
  new Employee('张行政', '行政部', 4000),
  new SalesEmployee('李销售', 5000, 0.1),
  new TechEmployee('王程序', 8000, 3),
  new ManagerEmployee('赵经理', 12000, 3000)
]

// 为销售员工添加业绩
const salesEmployee = employees[1] as SalesEmployee
salesEmployee.recordSale(50000)

// 为技术员工分配项目
const techEmployee = employees[2] as TechEmployee
techEmployee.assignProject('用户管理系统')
techEmployee.assignProject('数据分析平台')

// 为管理者添加团队成员
const manager = employees[3] as ManagerEmployee
manager.addTeamMember(employees[0])
manager.addTeamMember(employees[1])

// 多态的威力:同样的方法调用,不同的实现结果
console.log('\n=== 员工工资计算(多态演示)===')
employees.forEach((employee, index) => {
  console.log(`${index + 1}. ${employee.getInfo()}`)
  console.log(`   工资:${employee.calculateSalary()}元`)
  console.log(`   工作年限:${employee.getWorkYears()}年`)
  console.log('---')
})

继承和多态的核心价值:

  1. 代码复用:所有员工类都继承了基础的 namedepartmentgetInfo() 等成员
  2. 方法重写:每种员工都有自己独特的工资计算方式,体现了多态性
  3. 统一接口:虽然实现不同,但都可以调用相同的方法名,简化了使用
  4. 扩展性强:可以轻松添加新的员工类型,如实习生、顾问等
  5. 维护性好:修改共同功能时只需修改基类

设计原则体现:

  • 开闭原则:对扩展开放,对修改关闭
  • 里氏替换原则:子类可以替换父类使用
  • 依赖倒置原则:依赖抽象而不是具体实现

1.4 抽象类与接口实现的深度剖析

抽象类(Abstract Class)的设计哲学

抽象类是面向对象设计中的一个重要概念,它介于普通类和接口之间。抽象类的设计哲学是:定义一个不完整的类,为子类提供共同的基础结构和部分实现,同时强制子类实现某些关键方法

抽象类的核心特点:

  • 不能直接实例化 - 它是一个不完整的类,必须通过子类来使用
  • 可以包含抽象方法 - 只有声明没有实现,强制子类实现
  • 可以包含具体方法 - 提供公共功能,子类可以直接使用
  • 可以有构造函数 - 为子类提供初始化逻辑
  • 可以有属性 - 定义子类的公共数据结构

接口(Interface)的设计理念

接口定义了一个契约或协议,它规定了实现该接口的类必须具备哪些能力。接口的设计理念是:关注行为而不是实现,定义"能做什么"而不是"怎么做"

接口的核心特点:

  • 纯粹的契约 - 只定义结构,不包含任何实现
  • 多重实现 - 一个类可以实现多个接口
  • 接口继承 - 接口可以继承其他接口
  • 声明合并 - 同名接口会自动合并

让我们通过一个图形绘制系统来深入理解抽象类和接口的应用:

// 抽象类 - 图形基类
// 为所有图形提供共同的基础结构和部分实现
abstract class Shape {
  protected name: string
  protected color: string = '默认色'
  protected position: { x: number; y: number } = { x: 0, y: 0 }
  
  constructor(name: string) {
    this.name = name
  }
  
  // 抽象方法 - 每种图形计算面积和周长的方式不同,必须由子类实现
  abstract calculateArea(): number
  abstract calculatePerimeter(): number
  
  // 具体方法 - 所有图形都有的通用功能,子类可以直接使用
  public getDescription(): string {
    const area = this.calculateArea().toFixed(2)
    const perimeter = this.calculatePerimeter().toFixed(2)
    return `这是一个${this.color}${this.name},面积:${area},周长:${perimeter}`
  }
  
  // 设置颜色 - 通用功能
  public setColor(color: string): void {
    this.color = color
    console.log(`${this.name}颜色已设置为${color}`)
  }
  
  // 设置位置 - 通用功能
  public setPosition(x: number, y: number): void {
    this.position = { x, y }
    console.log(`${this.name}位置已设置为(${x}, ${y})`)
  }
  
  // 获取基本信息
  public getInfo(): string {
    return `图形:${this.name},颜色:${this.color},位置:(${this.position.x}, ${this.position.y})`
  }
}

现在让我们实现具体的图形类:

// 圆形类 - 实现抽象类
class Circle extends Shape {
  private radius: number
  
  constructor(radius: number) {
    super('圆形') // 调用抽象类的构造函数
    this.radius = radius
  }
  
  // 必须实现的抽象方法 - 圆形面积计算
  public calculateArea(): number {
    return Math.PI * this.radius * this.radius
  }
  
  // 必须实现的抽象方法 - 圆形周长计算
  public calculatePerimeter(): number {
    return 2 * Math.PI * this.radius
  }
  
  // 圆形特有的方法
  public getRadius(): number {
    return this.radius
  }
  
  public setRadius(radius: number): void {
    if (radius <= 0) {
      throw new Error('半径必须大于0')
    }
    this.radius = radius
    console.log(`圆形半径已设置为${radius}`)
  }
}

// 矩形类 - 另一个抽象类实现
class Rectangle extends Shape {
  private width: number
  private height: number
  
  constructor(width: number, height: number) {
    super('矩形')
    this.width = width
    this.height = height
  }
  
  // 实现抽象方法
  public calculateArea(): number {
    return this.width * this.height
  }
  
  public calculatePerimeter(): number {
    return 2 * (this.width + this.height)
  }
  
  // 矩形特有的方法
  public getDimensions(): { width: number; height: number } {
    return { width: this.width, height: this.height }
  }
  
  public resize(width: number, height: number): void {
    this.width = width
    this.height = height
    console.log(`矩形尺寸已调整为${width}x${height}`)
  }
}

现在让我们定义一些接口,展示接口的强大能力:

// 绘制能力接口
interface Drawable {
  draw(): void
  setColor(color: string): void
}

// 移动能力接口
interface Movable {
  moveTo(x: number, y: number): void
  getPosition(): { x: number; y: number }
}

// 缩放能力接口
interface Resizable {
  resize(scale: number): void
  getScale(): number
}

// 动画能力接口
interface Animatable {
  startAnimation(): void
  stopAnimation(): void
  isAnimating(): boolean
}

现在让我们创建一个实现多个接口的高级图形类:

// 交互式圆形 - 继承抽象类并实现多个接口
class InteractiveCircle extends Circle implements Drawable, Movable, Resizable, Animatable {
  private scale: number = 1
  private isAnimationRunning: boolean = false
  private animationTimer?: NodeJS.Timeout
  
  constructor(radius: number, color: string = '蓝色') {
    super(radius)
    this.setColor(color)
  }
  
  // 实现 Drawable 接口
  public draw(): void {
    const pos = this.getPosition()
    const actualRadius = this.getRadius() * this.scale
    console.log(`在坐标(${pos.x}, ${pos.y})绘制一个${this.color}的圆形,半径:${actualRadius.toFixed(2)}`)
  }
  
  // 实现 Movable 接口
  public moveTo(x: number, y: number): void {
    const currentPos = this.getPosition()
    console.log(`圆形从(${currentPos.x}, ${currentPos.y})移动到(${x}, ${y})`)
    this.setPosition(x, y)
  }
  
  public getPosition(): { x: number; y: number } {
    return { ...this.position } // 返回副本,保护内部数据
  }
  
  // 实现 Resizable 接口
  public resize(scale: number): void {
    if (scale <= 0) {
      throw new Error('缩放比例必须大于0')
    }
    console.log(`圆形从缩放比例${this.scale}调整到${scale}`)
    this.scale = scale
  }
  
  public getScale(): number {
    return this.scale
  }
  
  // 实现 Animatable 接口
  public startAnimation(): void {
    if (this.isAnimationRunning) {
      console.log('动画已在运行中')
      return
    }
    
    this.isAnimationRunning = true
    console.log('开始动画效果')
    
    let angle = 0
    this.animationTimer = setInterval(() => {
      angle += 0.1
      const x = Math.cos(angle) * 50
      const y = Math.sin(angle) * 50
      this.setPosition(x, y)
      this.draw()
    }, 100)
  }
  
  public stopAnimation(): void {
    if (!this.isAnimationRunning) {
      console.log('动画未在运行')
      return
    }
    
    this.isAnimationRunning = false
    if (this.animationTimer) {
      clearInterval(this.animationTimer)
      this.animationTimer = undefined
    }
    console.log('动画已停止')
  }
  
  public isAnimating(): boolean {
    return this.isAnimationRunning
  }
  
  // 组合功能的高级方法
  public performComplexAction(): void {
    console.log('=== 执行复杂动作序列 ===')
    this.draw()
    this.moveTo(10, 10)
    this.resize(1.5)
    this.setColor('红色')
    this.draw()
    this.startAnimation()
    
    // 3秒后停止动画
    setTimeout(() => {
      this.stopAnimation()
      console.log('=== 复杂动作序列完成 ===')
    }, 3000)
  }
}

使用示例和多态演示:

console.log('=== 抽象类使用示例 ===')
// const shape = new Shape("测试"); // ❌ 错误!不能直接实例化抽象类

// 创建具体的图形实例
const circle = new Circle(5)
circle.setColor('绿色')
console.log(circle.getDescription())

const rectangle = new Rectangle(4, 6)
rectangle.setColor('紫色')
console.log(rectangle.getDescription())

console.log('\n=== 接口实现示例 ===')
const interactiveCircle = new InteractiveCircle(3, '橙色')
interactiveCircle.performComplexAction()

console.log('\n=== 多态演示(抽象类) ===')
const shapes: Shape[] = [
  new Circle(4), 
  new Rectangle(3, 5), 
  new InteractiveCircle(2, '青色')
]

shapes.forEach((shape, index) => {
  console.log(`图形${index + 1}: ${shape.getDescription()}`)
})

console.log('\n=== 接口多态演示 ===')
// 将实现了相同接口的对象放在一起使用
const drawableObjects: Drawable[] = [
  new InteractiveCircle(2, '红色'),
  new InteractiveCircle(3, '蓝色')
]

drawableObjects.forEach(obj => {
  obj.draw() // 多态调用
})

抽象类 vs 接口的选择指南:

特性抽象类接口
实例化不能直接实例化不能实例化
方法实现可以有具体方法和抽象方法只能有方法签名
属性可以有实例属性只能有属性签名
构造函数可以有构造函数不能有构造函数
继承数量单继承多实现
扩展性通过继承扩展通过实现和继承扩展
使用场景有共同属性和部分共同行为定义能力契约

设计建议:

  1. 使用抽象类的场景

    • 多个类有共同的属性和部分共同的方法实现
    • 需要为子类提供默认实现
    • 希望强制子类实现某些关键方法
  2. 使用接口的场景

    • 定义类必须具备的能力
    • 需要多重继承的效果
    • 希望实现松耦合的设计
  3. 组合使用

    • 抽象类提供基础结构和共同实现
    • 接口定义额外的能力和契约
    • 通过继承抽象类和实现接口获得最大的灵活性

通过这个详细的例子,我们可以看到抽象类和接口如何协同工作,为我们提供了强大而灵活的面向对象设计能力。

1.5 成员可见性修饰符详解

访问修饰符的设计意义

访问修饰符是面向对象编程中实现封装性的重要工具。它们帮助我们控制类成员的可见性,从而保护内部实现细节,防止外部代码的不当访问和修改。这种设计遵循了"最小权限原则"——只暴露必要的接口,隐藏实现细节。

TypeScript 提供的四种访问修饰符:

  1. public(公共):任何地方都可以访问,这是默认的访问级别
  2. private(私有):只能在定义它的类内部访问
  3. protected(受保护):可以在类及其子类中访问
  4. readonly(只读):只能在声明时或构造函数中赋值

访问修饰符在实际开发中的重要性:

在大型软件项目中,访问修饰符不仅仅是语法特性,更是架构设计的重要工具。它们帮助我们:

  • 建立清晰的API边界 - 明确哪些是对外接口,哪些是内部实现
  • 防止意外的依赖关系 - 避免外部代码依赖不稳定的内部实现
  • 提高代码的可维护性 - 内部实现的修改不会影响外部使用者
  • 增强代码的安全性 - 保护敏感数据和关键逻辑不被误用

让我们通过一个银行账户系统来深入理解访问修饰符的实际应用:

// 银行账户类 - 展示不同访问修饰符的使用
class BankAccount {
  // 公共属性 - 外部可以访问的基本信息
  public accountHolder: string
  public accountType: string
  
  // 受保护属性 - 子类可以访问,但外部不能直接访问
  protected balance: number
  protected interestRate: number
  
  // 私有属性 - 只有本类内部可以访问的敏感信息
  private accountNumber: string
  private pin: string
  private transactionHistory: Transaction[]
  
  // 只读属性 - 一旦设置就不能修改的重要信息
  readonly bankCode: string
  readonly creationDate: Date
  
  constructor(
    accountHolder: string, 
    accountType: string, 
    initialBalance: number,
    pin: string
  ) {
    this.accountHolder = accountHolder
    this.accountType = accountType
    this.balance = initialBalance
    this.pin = pin
    this.interestRate = 0.02 // 默认2%年利率
    this.accountNumber = this.generateAccountNumber()
    this.transactionHistory = []
    this.bankCode = 'BANK001'
    this.creationDate = new Date()
  }
  
  // 公共方法 - 对外提供的服务接口
  public getAccountInfo(): string {
    return `账户持有人: ${this.accountHolder}, 账户类型: ${this.accountType}`
  }
  
  public checkBalance(inputPin: string): number | null {
    if (this.validatePin(inputPin)) {
      return this.balance
    }
    console.log('PIN码错误,无法查询余额')
    return null
  }
  
  public deposit(amount: number, pin: string): boolean {
    if (!this.validatePin(pin)) {
      console.log('PIN码错误,存款失败')
      return false
    }
    
    if (amount <= 0) {
      console.log('存款金额必须大于0')
      return false
    }
    
    this.balance += amount
    this.recordTransaction('存款', amount)
    console.log(`存款成功,当前余额: ${this.balance}`)
    return true
  }
  
  public withdraw(amount: number, pin: string): boolean {
    if (!this.validatePin(pin)) {
      console.log('PIN码错误,取款失败')
      return false
    }
    
    if (amount <= 0) {
      console.log('取款金额必须大于0')
      return false
    }
    
    if (amount > this.balance) {
      console.log('余额不足,取款失败')
      return false
    }
    
    this.balance -= amount
    this.recordTransaction('取款', amount)
    console.log(`取款成功,当前余额: ${this.balance}`)
    return true
  }
  
  // 受保护方法 - 子类可以使用的工具方法
  protected calculateInterest(): number {
    return this.balance * this.interestRate
  }
  
  protected updateBalance(newBalance: number): void {
    this.balance = newBalance
    this.recordTransaction('余额更新', newBalance)
  }
  
  // 私有方法 - 只有本类内部使用的实现细节
  private validatePin(inputPin: string): boolean {
    return this.pin === inputPin
  }
  
  private generateAccountNumber(): string {
    return 'ACC' + Date.now().toString().slice(-8)
  }
  
  private recordTransaction(type: string, amount: number): void {
    const transaction: Transaction = {
      id: Date.now().toString(),
      type,
      amount,
      timestamp: new Date(),
      balance: this.balance
    }
    this.transactionHistory.push(transaction)
  }
}

// 交易记录接口
interface Transaction {
  id: string
  type: string
  amount: number
  timestamp: Date
  balance: number
}

现在让我们创建一个储蓄账户类,展示继承中访问修饰符的作用:

// 储蓄账户类 - 继承银行账户
class SavingsAccount extends BankAccount {
  private minimumBalance: number
  
  constructor(
    accountHolder: string, 
    initialBalance: number,
    pin: string,
    minimumBalance: number = 100
  ) {
    super(accountHolder, '储蓄账户', initialBalance, pin)
    this.minimumBalance = minimumBalance
    // ✅ 可以访问受保护属性
    this.interestRate = 0.035 // 储蓄账户利率更高
  }
  
  // 重写取款方法,添加最低余额限制
  public withdraw(amount: number, pin: string): boolean {
    // ✅ 可以访问受保护属性进行检查
    if (this.balance - amount < this.minimumBalance) {
      console.log(`取款失败:余额不能低于最低限额 ${this.minimumBalance}`)
      return false
    }
    
    // 调用父类的取款方法
    return super.withdraw(amount, pin)
  }
  
  // 储蓄账户特有的功能
  public applyInterest(pin: string): boolean {
    if (!this.validatePin(pin)) { // ❌ 编译错误:validatePin是私有方法
      console.log('PIN码错误,无法应用利息')
      return false
    }
    
    // ✅ 可以使用受保护方法
    const interest = this.calculateInterest()
    const newBalance = this.balance + interest
    
    // ✅ 可以使用受保护方法
    this.updateBalance(newBalance)
    console.log(`利息已应用,新余额: ${newBalance}`)
    return true
  }
  
  // 正确的利息应用方法
  public applyInterestCorrect(pin: string): boolean {
    // 通过公共方法验证PIN
    const currentBalance = this.checkBalance(pin)
    if (currentBalance === null) {
      return false
    }
    
    const interest = this.calculateInterest()
    const newBalance = this.balance + interest
    this.updateBalance(newBalance)
    console.log(`利息已应用,新余额: ${newBalance}`)
    return true
  }
}

使用示例和访问控制演示:

// 创建银行账户
const account = new BankAccount('张三', '活期账户', 1000, '1234')
const savings = new SavingsAccount('李四', 5000, '5678', 500)

// ✅ 可以访问公共成员
console.log(account.accountHolder)    // "张三"
console.log(account.getAccountInfo()) // "账户持有人: 张三, 账户类型: 活期账户"
console.log(account.bankCode)         // "BANK001" (只读属性)

// ✅ 可以调用公共方法
account.deposit(500, '1234')
account.withdraw(200, '1234')
console.log(account.checkBalance('1234')) // 1300

// ❌ 不能访问受保护成员
// console.log(account.balance)       // 编译错误:属性"balance"受保护
// account.calculateInterest()        // 编译错误:方法"calculateInterest"受保护

// ❌ 不能访问私有成员
// console.log(account.accountNumber) // 编译错误:属性"accountNumber"为私有
// account.validatePin('1234')        // 编译错误:方法"validatePin"为私有

// ❌ 不能修改只读属性
// account.bankCode = "NEW001"        // 编译错误:无法分配到"bankCode",因为它是只读属性

// 储蓄账户的特殊功能
savings.applyInterestCorrect('5678')
savings.withdraw(1000, '5678') // 会检查最低余额限制

访问修饰符的设计原则和最佳实践:

  1. 最小权限原则

    • 默认使用最严格的访问级别(private)
    • 只有在需要时才放宽权限
    • 避免过度暴露内部实现
  2. 接口设计原则

    • 公共方法应该提供清晰、稳定的API
    • 避免暴露可能变化的内部细节
    • 使用受保护成员为继承体系提供扩展点
  3. 数据保护原则

    • 敏感数据(如密码、账号)使用private
    • 业务数据(如余额)使用protected,允许子类访问
    • 配置数据(如利率)可以是protected或private
  4. 只读属性的使用

    • 用于不应该改变的重要标识(如账号、创建时间)
    • 提供数据完整性保证
    • 防止意外的数据修改

访问修饰符的实际价值总结:

修饰符访问范围使用场景设计价值
public任何地方对外API、配置属性开放接口,服务外部
protected类和子类继承体系内共享的数据和方法受控共享,支持扩展
private仅当前类内部实现、敏感数据完全封装,保护实现
readonly只读访问不可变的重要数据数据完整性,防止意外修改

通过合理使用访问修饰符,我们可以构建出既安全又灵活的类层次结构,这是面向对象设计的核心技能之一。

1.6 静态成员与实例成员的深入理解

静态成员 vs 实例成员的本质区别

在面向对象编程中,理解静态成员和实例成员的区别是非常重要的。这个区别不仅仅是语法上的,更是设计思想上的:

  • 实例成员:属于每个具体的对象实例,每个实例都有自己独立的一份数据
  • 静态成员:属于整个类,所有实例共享同一份数据,甚至不需要创建实例就可以使用

静态成员的使用场景:

  1. 工具函数 - 不依赖实例状态的纯函数
  2. 全局配置 - 整个类的配置参数
  3. 计数器 - 统计类的实例数量
  4. 缓存 - 类级别的数据缓存
  5. 工厂方法 - 创建实例的静态方法

让我们通过一个数学工具类来深入理解静态成员和实例成员的应用:

// 数学工具类 - 展示静态成员和实例成员的配合使用
class MathCalculator {
  // 静态属性 - 属于整个类,所有实例共享
  static readonly PI: number = 3.14159265359
  static readonly E: number = 2.71828182846
  static calculationCount: number = 0 // 统计总计算次数
  
  // 实例属性 - 属于每个具体的计算器实例
  public precision: number
  public history: string[] = []
  private instanceId: string
  
  constructor(precision: number = 2) {
    this.precision = precision
    this.instanceId = `calc_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`
    MathCalculator.calculationCount++ // 每创建一个实例,总数加1
  }
  
  // 静态方法 - 通过类名调用,不能访问实例成员
  static add(a: number, b: number): number {
    MathCalculator.calculationCount++ // ✅ 可以访问静态成员
    // this.precision // ❌ 不能访问实例成员,因为没有this上下文
    return a + b
  }
  
  static multiply(a: number, b: number): number {
    MathCalculator.calculationCount++
    return a * b
  }
  
  // 静态工具方法
  static isEven(num: number): boolean {
    return num % 2 === 0
  }
  
  static factorial(n: number): number {
    MathCalculator.calculationCount++
    if (n <= 1) return 1
    return n * MathCalculator.factorial(n - 1)
  }
  
  // 静态工厂方法 - 创建特定配置的实例
  static createHighPrecisionCalculator(): MathCalculator {
    return new MathCalculator(10)
  }
  
  static createBasicCalculator(): MathCalculator {
    return new MathCalculator(2)
  }
  
  // 实例方法 - 通过实例调用,可以访问所有成员
  public formatNumber(num: number): string {
    MathCalculator.calculationCount++ // ✅ 可以访问静态成员
    const formatted = num.toFixed(this.precision) // ✅ 可以访问实例成员
    this.addToHistory(`格式化数字: ${num} -> ${formatted}`) // ✅ 可以访问实例方法
    return formatted
  }
  
  public calculateCircleArea(radius: number): string {
    const area = MathCalculator.PI * radius * radius // 使用静态常量
    const result = this.formatNumber(area) // 使用实例方法格式化
    this.addToHistory(`圆形面积计算: 半径=${radius}, 面积=${result}`)
    return result
  }
  
  public power(base: number, exponent: number): string {
    const result = Math.pow(base, exponent)
    const formatted = this.formatNumber(result)
    this.addToHistory(`幂运算: ${base}^${exponent} = ${formatted}`)
    return formatted
  }
  
  // 实例方法 - 管理计算历史
  private addToHistory(operation: string): void {
    this.history.push(`[${this.instanceId}] ${operation}`)
  }
  
  public getHistory(): string[] {
    return [...this.history] // 返回副本,保护内部数据
  }
  
  public clearHistory(): void {
    this.history = []
    console.log(`计算器 ${this.instanceId} 的历史记录已清空`)
  }
  
  // 实例方法 - 获取实例信息
  public getInstanceInfo(): string {
    return `计算器ID: ${this.instanceId}, 精度: ${this.precision}, 历史记录: ${this.history.length}条`
  }
  
  // 静态方法 - 获取类级别的统计信息
  static getGlobalStats(): string {
    return `总计算次数: ${MathCalculator.calculationCount}`
  }
  
  // 静态方法 - 重置全局计数器
  static resetGlobalCounter(): void {
    MathCalculator.calculationCount = 0
    console.log('全局计算计数器已重置')
  }
}

使用示例和对比演示:

console.log('=== 静态成员使用示例 ===')

// 使用静态常量 - 不需要创建实例
console.log('圆周率:', MathCalculator.PI)
console.log('自然常数:', MathCalculator.E)

// 使用静态方法 - 通过类名直接调用
console.log('2 + 3 =', MathCalculator.add(2, 3))
console.log('4 * 5 =', MathCalculator.multiply(4, 5))
console.log('6是偶数吗?', MathCalculator.isEven(6))
console.log('5的阶乘:', MathCalculator.factorial(5))

// 查看全局统计
console.log(MathCalculator.getGlobalStats()) // "总计算次数: 5"

console.log('\n=== 实例成员使用示例 ===')

// 创建不同精度的计算器实例
const basicCalc = new MathCalculator(2)
const preciseCalc = new MathCalculator(6)

// 使用工厂方法创建实例
const highPrecCalc = MathCalculator.createHighPrecisionCalculator()

// 每个实例有自己独立的状态
console.log('基础计算器信息:', basicCalc.getInstanceInfo())
console.log('精确计算器信息:', preciseCalc.getInstanceInfo())
console.log('高精度计算器信息:', highPrecCalc.getInstanceInfo())

// 实例方法调用
console.log('基础计算器 - 圆面积:', basicCalc.calculateCircleArea(5))
console.log('精确计算器 - 圆面积:', preciseCalc.calculateCircleArea(5))
console.log('高精度计算器 - 2^10:', highPrecCalc.power(2, 10))

// 查看各自的历史记录
console.log('\n基础计算器历史:')
basicCalc.getHistory().forEach(record => console.log('  ', record))

console.log('\n精确计算器历史:')
preciseCalc.getHistory().forEach(record => console.log('  ', record))

// 全局统计会反映所有计算
console.log('\n' + MathCalculator.getGlobalStats())

console.log('\n=== 静态与实例成员的对比 ===')

// 静态成员:所有实例共享
console.log('创建实例前的计算次数:', MathCalculator.calculationCount)
const calc1 = new MathCalculator(3)
const calc2 = new MathCalculator(4)
console.log('创建2个实例后的计算次数:', MathCalculator.calculationCount)

// 实例成员:每个实例独立
calc1.calculateCircleArea(3)
calc2.calculateCircleArea(4)

console.log('calc1的历史记录数:', calc1.getHistory().length)
console.log('calc2的历史记录数:', calc2.getHistory().length)
console.log('全局计算次数:', MathCalculator.calculationCount)

静态成员和实例成员的设计原则:

  1. 静态成员适用场景

    • 工具函数:不依赖实例状态的纯函数
    • 常量:类相关的不变值(如PI、配置参数)
    • 全局状态:需要在所有实例间共享的数据
    • 工厂方法:创建实例的便捷方法
  2. 实例成员适用场景

    • 实例状态:每个对象独有的数据
    • 行为方法:依赖实例状态的操作
    • 配置属性:每个实例可以不同的设置
  3. 设计建议

    • 优先考虑实例成员,除非确实需要类级别的功能
    • 静态方法不应该访问实例成员
    • 使用静态工厂方法提供创建实例的便捷方式
    • 静态属性常用于存储常量和全局配置

内存和性能考虑:

特性静态成员实例成员
内存占用类加载时创建一份每个实例一份
访问速度稍快(无需实例化)正常
生命周期程序运行期间一直存在随实例创建和销毁
适用场景工具方法、常量、全局状态实例数据、行为方法

通过合理使用静态成员和实例成员,我们可以设计出既高效又清晰的类结构,这是面向对象设计的重要技能。


2. 模块系统与命名空间 📦

2.1 ES6 模块系统的核心理念

什么是模块?为什么需要模块化?

在软件开发的早期,所有代码都写在一个文件中,随着项目规模的增长,这种做法变得越来越难以维护。模块化编程的出现解决了这个问题。模块(Module)是代码组织的基本单元,它将相关的功能封装在一起,对外提供清晰的接口。

模块化编程的历史演进:

  1. 无模块时代:所有代码在全局作用域,命名冲突严重
  2. IIFE时代:使用立即执行函数创建局部作用域
  3. CommonJS/AMD时代:Node.js和浏览器的模块化尝试
  4. ES6模块时代:语言原生支持,成为标准

模块系统解决的核心问题:

  1. 命名空间污染:避免全局变量冲突
  2. 依赖管理:明确模块间的依赖关系
  3. 代码复用:一次编写,多处使用
  4. 按需加载:只加载需要的功能,优化性能
  5. 团队协作:不同开发者可以独立开发不同模块
  6. 代码维护:模块化的代码更容易理解和修改

TypeScript 模块系统的特殊优势:

TypeScript 完全支持 ES6 模块语法,并在此基础上提供了额外的类型安全保障:

  • 编译时类型检查:在编译阶段就能发现模块间的类型错误
  • 智能提示:IDE 可以提供精确的代码补全和重构支持
  • 接口导出:可以导出类型定义,实现类型的复用
  • 声明文件:为 JavaScript 库提供类型定义

让我们通过一个实际的例子来理解模块的基本使用:

// 文件:utils/math.ts - 数学工具模块
// 常量导出
export const PI = 3.14159265359
export const E = 2.71828182846

// 具名函数导出
export function add(a: number, b: number): number {
  return a + b
}

export function multiply(a: number, b: number): number {
  return a * b
}

// 默认导出 - 每个模块只能有一个
export default function subtract(a: number, b: number): number {
  return a - b
}

导入的多种方式和使用场景:

// 1. 具名导入 - 明确指定需要的功能
import { add, multiply, PI } from './utils/math'

// 2. 重命名导入 - 避免命名冲突
import { add as sum, multiply as product } from './utils/math'

// 3. 默认导入 - 可以任意命名
import subtract from './utils/math'
import minus from './utils/math' // 同样有效

// 4. 命名空间导入 - 导入所有导出内容
import * as MathUtils from './utils/math'

// 5. 混合导入 - 同时导入默认和具名导出
import subtract, { add, PI } from './utils/math'

不同导入方式的使用示例:

// 使用具名导入
console.log(add(2, 3))        // 5
console.log(multiply(4, 5))   // 20

// 使用重命名导入
console.log(sum(2, 3))        // 5
console.log(product(4, 5))    // 20

// 使用默认导入
console.log(subtract(10, 3))  // 7

// 使用命名空间导入
console.log(MathUtils.add(2, 3))        // 5
console.log(MathUtils.PI)               // 3.14159265359
console.log(MathUtils.default(10, 3))   // 7 (默认导出)

模块导出的设计原则:

  1. 具名导出适用场景

    • 工具函数集合
    • 常量和配置
    • 多个相关的类或接口
    • 可以导出多个,导入时必须使用确切的名称
  2. 默认导出适用场景

    • 模块的主要功能或类
    • 单一职责的模块
    • 每个模块只能有一个,导入时可以任意命名
  3. 混合导出策略

    • 主要功能使用默认导出
    • 辅助功能和配置使用具名导出
    • 提供最大的使用灵活性

模块系统的核心价值体现:

通过这个简单的数学工具模块例子,我们可以看到模块系统的价值:

  • 清晰的边界:每个模块有明确的功能范围
  • 可控的接口:只暴露需要的功能,隐藏实现细节
  • 灵活的使用:使用者可以按需导入,避免不必要的依赖
  • 类型安全:TypeScript 确保导入导出的类型匹配

2.2 高级模块特性

重新导出(Re-export)

重新导出让我们可以创建统一的入口点,简化导入路径:

// 文件:utils/string.ts
export function capitalize(str: string): string {
  return str.charAt(0).toUpperCase() + str.slice(1)
}

export function reverse(str: string): string {
  return str.split('').reverse().join('')
}

// 文件:utils/number.ts
export function isEven(num: number): boolean {
  return num % 2 === 0
}

export function random(min: number, max: number): number {
  return Math.floor(Math.random() * (max - min + 1)) + min
}

// 文件:utils/index.ts - 统一入口
export * from './string'    // 重新导出所有string工具
export * from './number'    // 重新导出所有number工具

// 也可以选择性重新导出
export { capitalize as cap } from './string'

使用重新导出:

// 现在可以从一个地方导入所有工具
import { capitalize, reverse, isEven, random } from './utils'

console.log(capitalize('hello'))  // "Hello"
console.log(isEven(4))           // true

动态导入(Dynamic Import)

动态导入允许我们按需加载模块,这对于代码分割和懒加载很有用:

// 条件导入
async function loadUtilsIfNeeded() {
  if (someCondition) {
    const utils = await import('./utils')
    return utils.capitalize('hello')
  }
}

// 动态导入在函数中使用
async function processData(data: any[]) {
  if (data.length > 1000) {
    // 只有在处理大量数据时才加载重型工具
    const { heavyProcessor } = await import('./heavy-utils')
    return heavyProcessor(data)
  }
  return data
}

2.3 模块解析策略

TypeScript 支持两种模块解析策略:

  1. Classic - 传统解析策略(已弃用)
  2. Node - 模仿 Node.js 的解析策略(推荐)

相对导入 vs 非相对导入:

// 相对导入 - 相对于当前文件的路径
import { User } from './models/User'        // 同级目录
import { Config } from '../config/app'     // 上级目录
import { Utils } from '../../shared/utils' // 上两级目录

// 非相对导入 - 从 node_modules 或基础路径解析
import { lodash } from 'lodash'             // 第三方库
import { ApiService } from '@/services/api' // 使用路径映射

路径映射配置(tsconfig.json):

{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@/*": ["*"],                    // @/components -> src/components
      "@models/*": ["models/*"],       // @models/User -> src/models/User
      "@utils/*": ["shared/utils/*"]   // @utils/math -> src/shared/utils/math
    }
  }
}

2.4 命名空间详解

什么是命名空间?

命名空间是TypeScript特有的代码组织方式,主要用于全局代码的组织和避免命名冲突。

命名空间的特点:

  • 全局作用域:在全局范围内创建逻辑分组
  • 嵌套支持:可以创建层次化的命名空间结构
  • 声明合并:同名命名空间会自动合并
  • 编译输出:编译为 IIFE(立即执行函数表达式)
// 基础命名空间
namespace Geometry {
  export interface Point {
    x: number
    y: number
  }

  export class Circle {
    constructor(
      public center: Point,
      public radius: number
    ) {}

    public area(): number {
      return Math.PI * this.radius * this.radius
    }
  }

  export function distance(p1: Point, p2: Point): number {
    return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2))
  }

  // 嵌套命名空间
  export namespace Utils {
    export function createPoint(x: number, y: number): Point {
      return { x, y }
    }

    export function createCircle(x: number, y: number, radius: number): Circle {
      return new Circle(createPoint(x, y), radius)
    }
  }
}

// 命名空间合并
namespace Geometry {
  export class Rectangle {
    constructor(
      public width: number,
      public height: number
    ) {}

    public area(): number {
      return this.width * this.height
    }
  }
}

使用命名空间:

// 使用命名空间中的类型和函数
const point: Geometry.Point = { x: 0, y: 0 }
const circle = new Geometry.Circle(point, 5)
const rect = new Geometry.Rectangle(10, 20)

console.log(circle.area())    // 78.54
console.log(rect.area())      // 200

// 使用嵌套命名空间
const newCircle = Geometry.Utils.createCircle(10, 10, 3)
const distance = Geometry.distance(point, { x: 3, y: 4 })

命名空间 vs 模块的选择指南:

特性命名空间模块
作用域全局文件级
加载方式立即加载按需加载
工具支持有限完整
代码分割不支持支持
推荐场景全局工具库现代应用开发

现代开发建议:

  • 新项目优先使用模块系统
  • 命名空间主要用于全局类型声明库的类型定义
  • 避免在模块化项目中混用命名空间

3. 装饰器与元编程 ✨

3.1 装饰器的本质与设计哲学

什么是装饰器?为什么需要装饰器?

装饰器(Decorator)是一种设计模式,也是 TypeScript 中的一个实验性特性。它允许我们在不修改原有代码的情况下,为类、方法、属性或参数添加额外的功能。装饰器的核心思想来源于"装饰者模式",这是面向对象设计中的一个重要模式。

装饰器解决的核心问题:

在传统的面向对象编程中,我们经常遇到这样的问题:

  1. 横切关注点:日志记录、性能监控、权限验证等功能需要在多个地方重复实现
  2. 代码耦合:业务逻辑与辅助功能混合在一起,难以维护
  3. 功能扩展:为现有类添加新功能时,需要修改原有代码
  4. 代码重复:相同的辅助功能在多个类中重复编写

装饰器的设计哲学:

装饰器遵循几个重要的设计原则:

  • 开闭原则:对扩展开放,对修改关闭
  • 单一职责原则:每个装饰器只关注一个特定的功能
  • 依赖倒置原则:依赖抽象而不是具体实现
  • 组合优于继承:通过组合多个装饰器实现复杂功能

生活中的装饰器类比:

为了更好地理解装饰器,我们可以用生活中的例子来类比:

  1. 咖啡店的咖啡制作

    • 基础咖啡(原始对象)
    • 加糖装饰器:为咖啡添加甜味
    • 加奶装饰器:为咖啡添加奶香
    • 加冰装饰器:将咖啡变成冰饮
    • 最终结果:冰糖奶咖啡
  2. 智能手机的功能扩展

    • 基础手机(原始对象)
    • 保护壳装饰器:增加防摔功能
    • 钢化膜装饰器:增加屏幕保护
    • 支架装饰器:增加观看便利性
    • 最终结果:功能完备的防护手机
  3. 汽车的改装升级

    • 基础汽车(原始对象)
    • 导航装饰器:增加导航功能
    • 行车记录仪装饰器:增加安全记录
    • 音响装饰器:提升音响效果
    • 最终结果:功能丰富的智能汽车

装饰器在软件开发中的价值:

  1. 功能增强:在不修改原有代码的基础上添加新功能
  2. 代码复用:同一个装饰器可以应用到多个不同的目标上
  3. 关注点分离:将业务逻辑与辅助功能分离,提高代码的可读性和可维护性
  4. 动态组合:可以根据需要动态组合不同的装饰器,实现灵活的功能配置
  5. 元编程能力:在编译时或运行时修改程序的行为

TypeScript 装饰器的特殊性:

TypeScript 的装饰器是实验性特性,但它们提供了强大的元编程能力:

  • 编译时处理:装饰器在编译时就会被处理,可以进行代码转换
  • 类型安全:结合 TypeScript 的类型系统,提供类型安全的装饰器
  • 反射支持:配合 reflect-metadata 库,可以获取丰富的类型信息
  • 多种目标:可以装饰类、方法、属性、参数等不同的目标

装饰器的分类和应用场景:

  1. 类装饰器:修改或扩展类的定义

    • 单例模式实现
    • 依赖注入
    • 类的元数据添加
  2. 方法装饰器:增强方法的功能

    • 日志记录
    • 性能监控
    • 缓存机制
    • 权限验证
  3. 属性装饰器:控制属性的行为

    • 数据验证
    • 序列化控制
    • 响应式数据
  4. 参数装饰器:处理方法参数

    • 参数验证
    • 依赖注入
    • 数据转换

启用装饰器的配置:

在使用 TypeScript 装饰器之前,需要在 tsconfig.json 中启用相关选项:

{
  "compilerOptions": {
    "experimentalDecorators": true,    // 启用装饰器支持
    "emitDecoratorMetadata": true,     // 启用装饰器元数据
    "target": "ES2015",                // 目标版本
    "lib": ["ES2015", "DOM"]           // 包含的库
  }
}

装饰器的执行时机和原理:

装饰器在 TypeScript 编译过程中会被转换为普通的 JavaScript 代码。理解装饰器的执行时机对于正确使用装饰器非常重要:

  1. 编译时执行:装饰器函数在类定义时就会执行
  2. 从下到上:多个装饰器的执行顺序是从下到上
  3. 参数传递:装饰器会接收不同的参数,用于获取目标对象的信息
  4. 返回值处理:装饰器的返回值会影响最终的结果

通过深入理解装饰器的设计哲学和应用场景,我们可以更好地利用这个强大的工具来构建灵活、可维护的代码结构。

3.2 方法装饰器的深入应用

方法装饰器的核心作用与设计思想

方法装饰器是装饰器体系中最常用也最实用的一种,它专门用于增强方法的功能。在企业级应用开发中,我们经常需要为方法添加各种横切关注点(Cross-cutting Concerns),比如:

  • 性能监控:记录方法执行时间,识别性能瓶颈
  • 日志记录:自动记录方法的调用情况和参数
  • 缓存机制:为计算密集型方法添加结果缓存
  • 错误处理:统一的异常处理和重试机制
  • 权限验证:在方法执行前检查用户权限
  • 数据验证:自动验证方法参数的合法性

方法装饰器的技术原理

方法装饰器接收三个参数:

  1. target:对于静态方法是类的构造函数,对于实例方法是类的原型对象
  2. propertyKey:方法的名称
  3. descriptor:方法的属性描述符,包含方法的实现

装饰器通过修改 descriptor.value 来替换原始方法,从而实现功能增强。这种方式遵循了代理模式(Proxy Pattern)的设计思想。

实际应用场景分析

让我们通过几个实际的业务场景来理解方法装饰器的价值:

  1. 性能优化场景:在电商系统中,商品推荐算法可能需要大量计算,我们需要监控其性能表现
  2. 缓存优化场景:数据库查询结果可以缓存,避免重复查询
  3. 可靠性保障场景:网络请求可能失败,需要自动重试机制

方法装饰器用于监视、修改或替换方法定义。

// 执行时间装饰器
function measureTime(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value

  descriptor.value = function (...args: any[]) {
    const start = performance.now()
    const result = originalMethod.apply(this, args)
    const end = performance.now()
    
    console.log(`${propertyKey} 执行时间: ${(end - start).toFixed(2)}ms`)
    return result
  }

  return descriptor
}

// 缓存装饰器
function cache(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value
  const cacheMap = new Map()
    
    descriptor.value = function (...args: any[]) {
    const key = JSON.stringify(args)
    
    if (cacheMap.has(key)) {
      console.log(`缓存命中: ${propertyKey}`)
      return cacheMap.get(key)
    }

    const result = originalMethod.apply(this, args)
    cacheMap.set(key, result)
    console.log(`缓存存储: ${propertyKey}`)
    return result
  }

  return descriptor
}

// 重试装饰器
function retry(maxAttempts: number = 3) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value

    descriptor.value = async function (...args: any[]) {
      let lastError: any
      
      for (let attempt = 1; attempt <= maxAttempts; attempt++) {
        try {
          return await originalMethod.apply(this, args)
        } catch (error) {
          lastError = error
          console.log(`${propertyKey}${attempt}次尝试失败:`, error.message)
          
          if (attempt < maxAttempts) {
            await new Promise(resolve => setTimeout(resolve, 1000 * attempt))
          }
        }
      }
      
      throw lastError
    }

    return descriptor
  }
}

// 使用装饰器的类
class DataProcessor {
  @measureTime
  @cache
  fibonacci(n: number): number {
    if (n <= 1) return n
    return this.fibonacci(n - 1) + this.fibonacci(n - 2)
  }

  @retry(3)
  async fetchData(url: string): Promise<any> {
    // 模拟可能失败的网络请求
    if (Math.random() < 0.7) {
      throw new Error('网络请求失败')
    }
    return { data: 'success', url }
  }
}

3.3 类装饰器的企业级应用

类装饰器的设计价值与应用场景

类装饰器是作用于整个类的装饰器,它可以监视、修改或完全替换类的定义。在大型企业应用中,类装饰器常用于实现一些架构级的功能模式:

主要应用场景:

  1. 设计模式实现:单例模式、工厂模式等
  2. 依赖注入:自动注入依赖的服务或配置
  3. 元数据管理:为类添加元数据信息,用于框架处理
  4. AOP切面编程:为类的所有方法添加统一的切面逻辑
  5. ORM映射:数据库实体类的自动映射配置
  6. API路由注册:Web框架中的路由自动注册

类装饰器的技术特点:

  • 接收一个参数:类的构造函数
  • 可以返回新的构造函数来替换原始类
  • 在类定义时执行,而不是实例化时
  • 可以修改类的原型,为所有实例添加共同功能

企业开发中的实际价值: 类装饰器帮助我们实现"配置即代码"的理念,通过简单的装饰器声明就能为类添加复杂的功能,大大减少了样板代码,提高了开发效率。

类装饰器用于监视、修改或替换类定义。

// 单例装饰器
function singleton<T extends { new (...args: any[]): {} }>(constructor: T) {
  let instance: any = null
  
  return class extends constructor {
    constructor(...args: any[]) {
      if (instance) {
        return instance
      }
      super(...args)
      instance = this
    }
  }
}

// 日志装饰器(记录类的创建)
function logged(className: string) {
    return function <T extends { new (...args: any[]): {} }>(constructor: T) {
        return class extends constructor {
      constructor(...args: any[]) {
        console.log(`创建${className}实例,参数:`, args)
        super(...args)
      }
    }
  }
}

// 自动绑定装饰器(绑定所有方法的this)
function autobind<T extends { new (...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    constructor(...args: any[]) {
      super(...args)
      
      // 自动绑定所有方法
      const proto = Object.getPrototypeOf(this)
      Object.getOwnPropertyNames(proto).forEach(name => {
        if (name !== 'constructor' && typeof this[name] === 'function') {
          this[name] = this[name].bind(this)
        }
      })
    }
  }
}

// 使用类装饰器
@singleton
@logged('配置管理器')
@autobind
class ConfigManager {
  private config: Record<string, any> = {}

  set(key: string, value: any): void {
    this.config[key] = value
    console.log(`设置配置: ${key} = ${value}`)
  }

  get(key: string): any {
    return this.config[key]
  }

  getHandler() {
    // 返回绑定了this的方法,可以安全地传递给其他函数
    return this.get
  }
}

3.4 属性装饰器的精细化控制

属性装饰器的设计理念与应用价值

属性装饰器专注于对类属性的精细化控制,它虽然不能直接修改属性的值,但可以通过元数据或属性描述符的方式来影响属性的行为。在现代Web开发中,属性装饰器常用于:

核心应用场景:

  1. 数据验证:为属性添加验证规则,确保数据的合法性
  2. 序列化控制:控制属性在JSON序列化时的行为
  3. 响应式数据:实现数据变化的自动监听和响应
  4. ORM字段映射:数据库字段与对象属性的映射关系
  5. API文档生成:为属性添加描述信息,自动生成API文档
  6. 权限控制:控制属性的读写权限

属性装饰器的技术特点:

  • 接收两个参数:目标对象和属性名
  • 主要用于添加元数据,而不是修改属性值
  • 通常与其他装饰器或框架配合使用
  • 在属性定义时执行,为后续处理做准备

与其他装饰器的协同作用: 属性装饰器往往不是独立工作的,它通常与方法装饰器、类装饰器配合,形成完整的功能体系。比如在数据验证场景中,属性装饰器定义验证规则,方法装饰器执行验证逻辑。

属性装饰器用于监视属性的声明。

// 只读装饰器
function readonly(target: any, propertyKey: string) {
  let value: any

  Object.defineProperty(target, propertyKey, {
    get() {
      return value
    },
    set(newValue: any) {
      if (value !== undefined) {
        throw new Error(`属性 ${propertyKey} 是只读的`)
      }
      value = newValue
    },
    enumerable: true,
    configurable: true
  })
}

// 验证装饰器
function validate(validator: (value: any) => boolean, message: string) {
  return function (target: any, propertyKey: string) {
    let value: any

    Object.defineProperty(target, propertyKey, {
      get() {
        return value
      },
      set(newValue: any) {
        if (!validator(newValue)) {
          throw new Error(`${propertyKey}: ${message}`)
        }
        value = newValue
      },
      enumerable: true,
      configurable: true
    })
  }
}

// 格式化装饰器
function format(formatter: (value: any) => any) {
  return function (target: any, propertyKey: string) {
    let value: any

    Object.defineProperty(target, propertyKey, {
      get() {
        return value
      },
      set(newValue: any) {
        value = formatter(newValue)
      },
      enumerable: true,
      configurable: true
    })
  }
}

// 使用属性装饰器
class User {
  @readonly
  id: number = Date.now()

  @validate(val => typeof val === 'string' && val.length > 0, '用户名不能为空')
  @format(val => val.trim().toLowerCase())
  username: string = ''

  @validate(val => /^[\w-]+@[\w-]+\.\w+$/.test(val), '邮箱格式不正确')
  email: string = ''

  @validate(val => val >= 18, '年龄必须大于等于18岁')
  age: number = 0
}

3.5 参数装饰器

参数装饰器用于监视参数的声明。

// 参数验证装饰器
function validateParam(validator: (value: any) => boolean, message: string) {
  return function (target: any, propertyKey: string, parameterIndex: number) {
    const existingValidators = Reflect.getMetadata('validators', target, propertyKey) || []
    existingValidators.push({ index: parameterIndex, validator, message })
    Reflect.defineMetadata('validators', existingValidators, target, propertyKey)
  }
}

// 方法装饰器:执行参数验证
function validateParams(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value
  const validators = Reflect.getMetadata('validators', target, propertyKey) || []

  descriptor.value = function (...args: any[]) {
    for (const { index, validator, message } of validators) {
      if (!validator(args[index])) {
        throw new Error(`参数${index + 1}: ${message}`)
      }
    }
    return originalMethod.apply(this, args)
  }

  return descriptor
}

// 使用参数装饰器
class MathService {
  @validateParams
  divide(
    @validateParam(val => typeof val === 'number', '必须是数字') a: number,
    @validateParam(val => typeof val === 'number' && val !== 0, '除数不能为0') b: number
  ): number {
    return a / b
  }

  @validateParams
  power(
    @validateParam(val => typeof val === 'number' && val >= 0, '底数必须是非负数') base: number,
    @validateParam(val => typeof val === 'number' && val >= 0, '指数必须是非负数') exponent: number
  ): number {
    return Math.pow(base, exponent)
  }
}

3.6 装饰器工厂和组合

装饰器工厂是返回装饰器的函数,可以接受配置参数:

// 装饰器工厂示例
function debounce(delay: number) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value
    let timeoutId: NodeJS.Timeout

    descriptor.value = function (...args: any[]) {
      clearTimeout(timeoutId)
      timeoutId = setTimeout(() => {
        originalMethod.apply(this, args)
      }, delay)
    }

    return descriptor
  }
}

function throttle(limit: number) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value
    let inThrottle = false

    descriptor.value = function (...args: any[]) {
      if (!inThrottle) {
        originalMethod.apply(this, args)
        inThrottle = true
        setTimeout(() => inThrottle = false, limit)
      }
    }

    return descriptor
  }
}

// 装饰器组合使用
class SearchComponent {
  @debounce(300)
  onSearchInput(query: string): void {
    console.log(`搜索: ${query}`)
  }

  @throttle(1000)
  onScroll(): void {
    console.log('页面滚动')
  }

  @measureTime
  @cache
  @retry(2)
  async searchData(query: string): Promise<any[]> {
    // 模拟搜索逻辑
    return []
  }
}

装饰器的执行顺序:

装饰器从下到上执行(就像洋葱模型):

class Example {
  @first
  @second
  @third
  method() {}
}

// 执行顺序:third -> second -> first

通过这些装饰器示例,我们可以看到装饰器的强大之处:它们让我们能够以声明式的方式为代码添加横切关注点,如日志记录、性能监控、缓存、验证等,而不需要修改业务逻辑代码本身。


4. 声明合并与类型扩展 🔗

4.1 接口合并

什么是声明合并?

声明合并是TypeScript的一个强大特性,它允许我们将多个同名的声明合并成一个单一的定义。最常见的是接口合并,这让我们可以在不同的地方扩展同一个接口。

接口合并示例:

// 基础用户接口
interface User {
  id: number
  name: string
}

// 在另一个地方扩展用户接口
interface User {
  email: string
  createdAt: Date
}

// 再次扩展
interface User {
  isActive: boolean
}

// 现在User接口包含所有属性
const user: User = {
  id: 1,
  name: '张三',
  email: 'zhangsan@example.com',
  createdAt: new Date(),
  isActive: true
}

4.2 模块扩展

扩展内置对象:

// 扩展String对象
declare global {
  interface String {
    toCapitalize(): string
  }
}

String.prototype.toCapitalize = function() {
  return this.charAt(0).toUpperCase() + this.slice(1).toLowerCase()
}

// 使用扩展方法
const text = 'hello world'
console.log(text.toCapitalize()) // "Hello world"

通过声明合并,我们可以灵活地扩展现有的类型定义,这在处理第三方库或内置对象时特别有用。


5. 枚举的艺术 🎯

5.1 枚举的设计理念与价值

什么是枚举?为什么它如此重要?

枚举(Enumeration,简称 Enum)是一种用于定义命名常量集合的数据类型。在软件开发中,我们经常需要表示一组相关的常量值,比如星期几、月份、状态码、颜色等。枚举为这些常量提供了类型安全、可读性强的解决方案。

枚举解决的核心问题:

在没有枚举的时代,开发者通常使用"魔法数字"或"魔法字符串"来表示常量:

  1. 魔法数字问题

    // 难以理解的魔法数字
    if (userRole === 1) { /* 1代表什么?管理员?普通用户? */ }
    if (orderStatus === 3) { /* 3是什么状态? */ }
    
  2. 魔法字符串问题

    // 容易出错的魔法字符串
    if (theme === "dark") { /* 如果拼写错误怎么办? */ }
    if (status === "in_progress") { /* 下划线还是驼峰? */ }
    
  3. 维护困难

    • 常量分散在代码各处,难以统一管理
    • 修改常量值时需要全局搜索替换
    • 没有编译时检查,容易出现运行时错误

枚举的设计价值:

枚举的出现解决了这些问题,并带来了额外的价值:

  1. 语义化:用有意义的名称代替难以理解的数字或字符串
  2. 类型安全:编译时检查,防止使用无效值
  3. 智能提示:IDE 可以提供准确的代码补全
  4. 集中管理:相关常量统一定义,便于维护
  5. 文档化:枚举本身就是很好的文档说明

枚举在不同编程语言中的演进:

  1. C语言时代:简单的整数常量集合
  2. Java/C#时代:面向对象的枚举,支持方法和属性
  3. TypeScript时代:结合了类型安全和灵活性

TypeScript 枚举的特殊优势:

TypeScript 的枚举系统比许多其他语言更加灵活和强大:

  • 多种类型支持:数字枚举、字符串枚举、异构枚举
  • 编译时优化:const 枚举可以在编译时内联
  • 反向映射:数字枚举支持从值到名称的反向查找
  • 类型推断:与 TypeScript 类型系统完美集成

让我们通过对比来看看枚举的价值:

没有枚举的痛苦:

// 不好的做法:使用魔法数字
const ORDER_PENDING = 0
const ORDER_CONFIRMED = 1
const ORDER_SHIPPED = 2
const ORDER_DELIVERED = 3
const ORDER_CANCELLED = 4

// 问题1:数字没有语义,难以理解
function processOrder(status: number) {
  if (status === 1) { /* 1代表什么状态?需要查文档或常量定义 */ }
}

// 问题2:容易传入无效值
processOrder(999) // 编译通过,但999不是有效的订单状态

// 问题3:没有智能提示
// IDE 无法知道应该传入什么值

有了枚举的清晰:

// 好的做法:使用枚举
enum OrderStatus {
  Pending,    // 0 - 等待处理
  Confirmed,  // 1 - 已确认
  Shipped,    // 2 - 已发货
  Delivered,  // 3 - 已送达
  Cancelled   // 4 - 已取消
}

// 优势1:语义清晰,一目了然
function processOrder(status: OrderStatus) {
  if (status === OrderStatus.Confirmed) { /* 清楚地知道这是确认状态 */ }
}

// 优势2:类型安全,防止无效值
processOrder(OrderStatus.Pending) // ✅ 正确
// processOrder(999) // ❌ 编译错误:类型不匹配

// 优势3:智能提示
// IDE 会提示所有可用的枚举值

枚举的应用场景:

  1. 状态管理:订单状态、用户状态、任务状态等
  2. 配置选项:主题设置、语言选择、权限级别等
  3. 常量定义:HTTP 状态码、错误代码、事件类型等
  4. 业务规则:优先级、分类、类型等

枚举设计的最佳实践:

  1. 命名规范

    • 枚举名使用 PascalCase(如 OrderStatus
    • 枚举值使用 PascalCase(如 Pending
    • 保持命名的一致性和语义性
  2. 值的选择

    • 数字枚举:适用于内部逻辑,性能优先
    • 字符串枚举:适用于外部交互,可读性优先
    • 避免混合类型,保持一致性
  3. 扩展性考虑

    • 预留扩展空间
    • 考虑向后兼容性
    • 避免删除或重新排序现有值

通过深入理解枚举的设计理念和价值,我们可以更好地利用这个工具来编写更加清晰、安全、可维护的代码。

5.2 数字枚举

数字枚举是最基本的枚举类型,成员会被自动赋予递增的数字值:

// 基础数字枚举
enum Direction {
  Up,    // 0
  Down,  // 1
  Left,  // 2
  Right  // 3
}

// 自定义起始值
enum HttpStatus {
    OK = 200,
    NotFound = 404,
  InternalServerError = 500
}

// 部分初始化(后续成员会自动递增)
enum Priority {
  Low = 1,    // 1
  Medium,     // 2
  High,       // 3
  Critical = 10, // 10
  Emergency   // 11
}

// 使用数字枚举
function move(direction: Direction): void {
  switch (direction) {
    case Direction.Up:
      console.log('向上移动')
      break
    case Direction.Down:
      console.log('向下移动')
      break
    case Direction.Left:
      console.log('向左移动')
      break
    case Direction.Right:
      console.log('向右移动')
      break
  }
}

move(Direction.Up)    // "向上移动"
move(0)               // 也可以直接使用数字值(不推荐)

数字枚举的反向映射:

enum Color {
  Red,
  Green,
  Blue
}

console.log(Color.Red)    // 0
console.log(Color[0])     // "Red"
console.log(Color[1])     // "Green"

// 枚举对象的实际结构
console.log(Color)
// {
//   0: "Red",
//   1: "Green", 
//   2: "Blue",
//   Red: 0,
//   Green: 1,
//   Blue: 2
// }

5.3 字符串枚举

字符串枚举的每个成员都必须用字符串字面量初始化:

// 字符串枚举
enum LogLevel {
  ERROR = 'error',
  WARN = 'warn',
  INFO = 'info',
  DEBUG = 'debug'
}

enum Theme {
  LIGHT = 'light',
  DARK = 'dark',
  AUTO = 'auto'
}

// 使用字符串枚举
function log(level: LogLevel, message: string): void {
  console.log(`[${level.toUpperCase()}] ${message}`)
}

log(LogLevel.ERROR, '这是一个错误信息')  // "[ERROR] 这是一个错误信息"

// 字符串枚举的优势:更好的调试体验
const currentTheme = Theme.DARK
console.log(currentTheme)  // "dark"(而不是数字)

5.4 异构枚举(混合枚举)

异构枚举包含数字和字符串成员:

// 异构枚举(不推荐,除非有特殊需求)
enum BooleanLikeHeterogeneousEnum {
  No = 0,
  Yes = "YES",
}

// 更实际的例子:API响应状态
enum ApiResponse {
  SUCCESS = 200,
  ERROR = 'ERROR',
  LOADING = 'LOADING',
  NOT_FOUND = 404
}

5.5 计算成员和常量成员

枚举成员可以是常量或计算值:

// 常量成员
enum FileAccess {
  // 常量成员
    None,
  Read = 1 << 1,     // 计算值:2
  Write = 1 << 2,    // 计算值:4
  ReadWrite = Read | Write, // 计算值:6
}

// 计算成员
enum E {
  A = getSomeValue(),  // 计算成员
  B,                   // 错误!计算成员后面必须有初始化器
}

function getSomeValue(): number {
  return 1
}

// 实际应用:权限系统
enum Permission {
  READ = 1,           // 001
  WRITE = 2,          // 010  
  EXECUTE = 4,        // 100
  READ_WRITE = READ | WRITE,        // 011
  READ_EXECUTE = READ | EXECUTE,    // 101
  WRITE_EXECUTE = WRITE | EXECUTE,  // 110
  ALL = READ | WRITE | EXECUTE      // 111
}

function hasPermission(userPermission: number, required: Permission): boolean {
  return (userPermission & required) === required
}

const userPerm = Permission.READ_WRITE
console.log(hasPermission(userPerm, Permission.READ))   // true
console.log(hasPermission(userPerm, Permission.EXECUTE)) // false

5.6 const 枚举

const 枚举在编译时会被完全删除,成员会被内联:

// 普通枚举
enum RegularEnum {
  A,
  B,
  C
}

// const 枚举
const enum ConstEnum {
  A,
  B,
  C
}

// 编译后的代码
let regularValue = RegularEnum.A    // 编译为:RegularEnum.A
let constValue = ConstEnum.A        // 编译为:0(直接内联)

// const 枚举的优势:更小的代码体积,更好的性能
const enum Direction {
  Up,
  Down,
  Left,
  Right
}

function move(direction: Direction): void {
  // 编译时这里会直接使用数字值
  if (direction === Direction.Up) {  // 编译为:if (direction === 0)
    console.log('向上移动')
  }
}

5.7 外部枚举

外部枚举用于描述已经存在的枚举类型:

// 外部枚举声明(通常在 .d.ts 文件中)
declare enum ExternalEnum {
  A = 1,
  B,
  C = 2
}

// 外部 const 枚举
declare const enum ExternalConstEnum {
  A,
  B,
  C
}

5.8 枚举的实际应用

游戏状态管理:

enum GameState {
  MENU = 'menu',
  PLAYING = 'playing',
  PAUSED = 'paused',
  GAME_OVER = 'game_over'
}

class Game {
  private state: GameState = GameState.MENU

  public setState(newState: GameState): void {
    console.log(`游戏状态从 ${this.state} 变更为 ${newState}`)
    this.state = newState
  }

  public getState(): GameState {
    return this.state
  }

  public canPause(): boolean {
    return this.state === GameState.PLAYING
  }
}

HTTP 状态码管理:

enum HttpStatusCode {
  // 成功状态
  OK = 200,
  CREATED = 201,
  NO_CONTENT = 204,
  
  // 重定向状态
  MOVED_PERMANENTLY = 301,
  FOUND = 302,
  
  // 客户端错误
  BAD_REQUEST = 400,
  UNAUTHORIZED = 401,
  FORBIDDEN = 403,
  NOT_FOUND = 404,
  
  // 服务器错误
  INTERNAL_SERVER_ERROR = 500,
  BAD_GATEWAY = 502,
  SERVICE_UNAVAILABLE = 503
}

class ApiResponse<T> {
  constructor(
    public status: HttpStatusCode,
    public data?: T,
    public message?: string
  ) {}

  public isSuccess(): boolean {
    return this.status >= 200 && this.status < 300
  }

  public isClientError(): boolean {
    return this.status >= 400 && this.status < 500
  }

  public isServerError(): boolean {
    return this.status >= 500
  }
}

// 使用示例
const response = new ApiResponse(HttpStatusCode.NOT_FOUND, null, '用户不存在')
console.log(response.isClientError())  // true

枚举的最佳实践:

  1. 优先使用字符串枚举:更好的调试体验和序列化支持
  2. 使用 const 枚举优化性能:在不需要反向映射时
  3. 避免异构枚举:除非有特殊业务需求
  4. 使用有意义的命名:让代码自文档化
  5. 考虑使用联合类型替代:在某些场景下更灵活
// 联合类型替代枚举的例子
type Theme = 'light' | 'dark' | 'auto'  // 更简洁,类型安全
const theme: Theme = 'dark'

6. 混入(Mixins)模式 🎭

6.1 什么是混入?

混入(Mixin)是一种设计模式,让我们可以将多个类的功能组合到一个类中。就像制作奶茶一样,你可以选择茶底(红茶、绿茶),然后加入不同的配料(珍珠、椰果、布丁)。

基础混入示例:

// 基础类型定义
type Constructor = new (...args: any[]) => {}

// 时间戳混入 - 给类添加时间追踪功能
function WithTimestamp<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    createdAt = new Date()
    
    getAge(): number {
      return Date.now() - this.createdAt.getTime()
    }
  }
}

// 基础用户类
class User {
  constructor(public name: string) {}
  
  greet(): string {
    return `Hello, I'm ${this.name}`
  }
}

// 使用混入创建增强的用户类
const TimestampedUser = WithTimestamp(User)

// 使用
const user = new TimestampedUser('张三')
console.log(user.greet())        // "Hello, I'm 张三"
console.log(user.getAge())       // 返回创建后经过的毫秒数

通过混入,我们可以灵活地组合不同的功能,创建具有多种能力的类。


7. 实战案例:企业级任务管理系统 🏢

7.1 系统架构设计

让我们综合运用前面学到的所有知识,构建一个完整的任务管理系统。这个系统将展示类、接口、枚举、装饰器、模块等特性的协同工作。

系统功能需求:

  • 任务的增删改查
  • 用户权限管理
  • 任务状态流转
  • 日志记录和性能监控
  • 数据验证和缓存

7.2 核心类型定义

// 枚举定义
enum TaskStatus {
  TODO = 'todo',
  IN_PROGRESS = 'in_progress',
  IN_REVIEW = 'in_review',
  DONE = 'done',
  CANCELLED = 'cancelled'
}

enum Priority {
  LOW = 1,
  MEDIUM = 2,
  HIGH = 3,
  URGENT = 4
}

enum UserRole {
  ADMIN = 'admin',
  MANAGER = 'manager',
  DEVELOPER = 'developer',
  VIEWER = 'viewer'
}

// 基础接口
interface IUser {
  id: string
  username: string
  email: string
  role: UserRole
  createdAt: Date
}

interface ITask {
  id: string
  title: string
  description?: string
  status: TaskStatus
  priority: Priority
  assigneeId?: string
  creatorId: string
  createdAt: Date
  updatedAt: Date
  dueDate?: Date
}

interface IComment {
  id: string
  taskId: string
  userId: string
  content: string
  createdAt: Date
}

// 权限接口
interface IPermissionChecker {
  canCreateTask(user: IUser): boolean
  canUpdateTask(user: IUser, task: ITask): boolean
  canDeleteTask(user: IUser, task: ITask): boolean
  canViewTask(user: IUser, task: ITask): boolean
}

7.3 装饰器定义

// 日志装饰器
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value
  
  descriptor.value = function (...args: any[]) {
    console.log(`[${new Date().toISOString()}] 调用方法: ${target.constructor.name}.${propertyKey}`)
    console.log(`参数:`, args)
    
    const result = originalMethod.apply(this, args)
    
    console.log(`返回结果:`, result)
    return result
  }
  
  return descriptor
}

// 权限检查装饰器
function requirePermission(permission: keyof IPermissionChecker) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value
    
    descriptor.value = function (user: IUser, ...args: any[]) {
      const permissionChecker: IPermissionChecker = this.permissionChecker
      
      if (!permissionChecker[permission](user, ...args)) {
        throw new Error(`用户 ${user.username} 没有执行 ${propertyKey} 的权限`)
      }
      
      return originalMethod.apply(this, [user, ...args])
    }
    
    return descriptor
  }
}

// 缓存装饰器
function cache(ttl: number = 60000) { // 默认缓存1分钟
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value
    const cacheMap = new Map<string, { data: any; timestamp: number }>()
    
    descriptor.value = function (...args: any[]) {
      const key = JSON.stringify(args)
      const cached = cacheMap.get(key)
      
      if (cached && Date.now() - cached.timestamp < ttl) {
        console.log(`缓存命中: ${propertyKey}`)
        return cached.data
      }
      
      const result = originalMethod.apply(this, args)
      cacheMap.set(key, { data: result, timestamp: Date.now() })
      console.log(`缓存存储: ${propertyKey}`)
      
      return result
    }
    
    return descriptor
  }
}

// 验证装饰器
function validate(validator: (value: any) => boolean, message: string) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value
    
    descriptor.value = function (...args: any[]) {
      for (const arg of args) {
        if (!validator(arg)) {
          throw new Error(`验证失败: ${message}`)
        }
      }
      return originalMethod.apply(this, args)
    }
    
    return descriptor
  }
}

7.4 权限管理系统

class PermissionChecker implements IPermissionChecker {
  canCreateTask(user: IUser): boolean {
    return [UserRole.ADMIN, UserRole.MANAGER, UserRole.DEVELOPER].includes(user.role)
  }
  
  canUpdateTask(user: IUser, task: ITask): boolean {
    if (user.role === UserRole.ADMIN) return true
    if (user.role === UserRole.MANAGER) return true
    if (user.role === UserRole.DEVELOPER && task.assigneeId === user.id) return true
    return false
  }
  
  canDeleteTask(user: IUser, task: ITask): boolean {
    if (user.role === UserRole.ADMIN) return true
    if (user.role === UserRole.MANAGER && task.creatorId === user.id) return true
    return false
  }
  
  canViewTask(user: IUser, task: ITask): boolean {
    // 所有用户都可以查看任务
    return true
  }
}

7.5 用户管理系统

class User implements IUser {
  public readonly id: string
  public readonly createdAt: Date
  
  constructor(
    public username: string,
    public email: string,
    public role: UserRole
  ) {
    this.id = this.generateId()
    this.createdAt = new Date()
  }
  
  private generateId(): string {
    return Date.now().toString(36) + Math.random().toString(36).substr(2)
  }
  
  public hasRole(role: UserRole): boolean {
    return this.role === role
  }
  
  public isAdmin(): boolean {
    return this.role === UserRole.ADMIN
  }
  
  public canManage(): boolean {
    return [UserRole.ADMIN, UserRole.MANAGER].includes(this.role)
  }
}

class UserManager {
  private users: Map<string, User> = new Map()
  
  @log
  createUser(username: string, email: string, role: UserRole): User {
    const user = new User(username, email, role)
    this.users.set(user.id, user)
    return user
  }
  
  @cache(30000) // 缓存30秒
  getUserById(id: string): User | undefined {
    return this.users.get(id)
  }
  
  @cache(30000)
  getUserByUsername(username: string): User | undefined {
    return Array.from(this.users.values()).find(user => user.username === username)
  }
  
  getAllUsers(): User[] {
    return Array.from(this.users.values())
  }
}

7.6 任务管理核心系统

class Task implements ITask {
  public readonly id: string
  public readonly createdAt: Date
  public updatedAt: Date
  
  constructor(
    public title: string,
    public creatorId: string,
    public priority: Priority = Priority.MEDIUM,
    public description?: string,
    public assigneeId?: string,
    public dueDate?: Date
  ) {
    this.id = this.generateId()
    this.createdAt = new Date()
    this.updatedAt = new Date()
    this.status = TaskStatus.TODO
  }
  
  public status: TaskStatus = TaskStatus.TODO
  
  private generateId(): string {
    return 'task_' + Date.now().toString(36) + Math.random().toString(36).substr(2)
  }
  
  public updateStatus(newStatus: TaskStatus): void {
    this.status = newStatus
    this.updatedAt = new Date()
  }
  
  public assign(userId: string): void {
    this.assigneeId = userId
    this.updatedAt = new Date()
  }
  
  public isOverdue(): boolean {
    return this.dueDate ? new Date() > this.dueDate : false
  }
  
  public getDaysUntilDue(): number | null {
    if (!this.dueDate) return null
    const timeDiff = this.dueDate.getTime() - new Date().getTime()
    return Math.ceil(timeDiff / (1000 * 3600 * 24))
  }
}

class TaskManager {
  private tasks: Map<string, Task> = new Map()
  private permissionChecker: IPermissionChecker
  
  constructor(permissionChecker: IPermissionChecker) {
    this.permissionChecker = permissionChecker
  }
  
  @log
  @requirePermission('canCreateTask')
  @validate(task => task.title && task.title.trim().length > 0, '任务标题不能为空')
  createTask(
    user: IUser,
    title: string,
    priority: Priority = Priority.MEDIUM,
    description?: string,
    assigneeId?: string,
    dueDate?: Date
  ): Task {
    const task = new Task(title, user.id, priority, description, assigneeId, dueDate)
    this.tasks.set(task.id, task)
    
    console.log(`任务创建成功: ${title} (ID: ${task.id})`)
    return task
  }
  
  @log
  @requirePermission('canUpdateTask')
  updateTaskStatus(user: IUser, taskId: string, newStatus: TaskStatus): Task {
    const task = this.getTaskById(taskId)
    if (!task) {
      throw new Error(`任务不存在: ${taskId}`)
    }
    
    const oldStatus = task.status
    task.updateStatus(newStatus)
    
    console.log(`任务状态更新: ${taskId} ${oldStatus} -> ${newStatus}`)
    return task
  }
  
  @log
  @requirePermission('canDeleteTask')
  deleteTask(user: IUser, taskId: string): boolean {
    const task = this.getTaskById(taskId)
    if (!task) {
      throw new Error(`任务不存在: ${taskId}`)
    }
    
    const deleted = this.tasks.delete(taskId)
    if (deleted) {
      console.log(`任务删除成功: ${taskId}`)
    }
    return deleted
  }
  
  @cache(10000) // 缓存10秒
  getTaskById(taskId: string): Task | undefined {
    return this.tasks.get(taskId)
  }
  
  @cache(5000)
  getTasksByStatus(status: TaskStatus): Task[] {
    return Array.from(this.tasks.values()).filter(task => task.status === status)
  }
  
  @cache(5000)
  getTasksByAssignee(assigneeId: string): Task[] {
    return Array.from(this.tasks.values()).filter(task => task.assigneeId === assigneeId)
  }
  
  @cache(5000)
  getOverdueTasks(): Task[] {
    return Array.from(this.tasks.values()).filter(task => task.isOverdue())
  }
  
  getTaskStatistics(): Record<TaskStatus, number> {
    const stats: Record<TaskStatus, number> = {
      [TaskStatus.TODO]: 0,
      [TaskStatus.IN_PROGRESS]: 0,
      [TaskStatus.IN_REVIEW]: 0,
      [TaskStatus.DONE]: 0,
      [TaskStatus.CANCELLED]: 0
    }
    
    for (const task of this.tasks.values()) {
      stats[task.status]++
    }
    
    return stats
  }
}

7.7 系统集成和使用示例

// 系统初始化
const permissionChecker = new PermissionChecker()
const userManager = new UserManager()
const taskManager = new TaskManager(permissionChecker)

// 创建用户
const admin = userManager.createUser('admin', 'admin@company.com', UserRole.ADMIN)
const manager = userManager.createUser('manager', 'manager@company.com', UserRole.MANAGER)
const dev1 = userManager.createUser('developer1', 'dev1@company.com', UserRole.DEVELOPER)
const dev2 = userManager.createUser('developer2', 'dev2@company.com', UserRole.DEVELOPER)

// 创建任务
console.log('=== 创建任务 ===')
const task1 = taskManager.createTask(
  manager,
  '实现用户登录功能',
  Priority.HIGH,
  '需要实现JWT认证和权限验证',
  dev1.id,
  new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) // 7天后到期
)

const task2 = taskManager.createTask(
  manager,
  '优化数据库查询性能',
  Priority.MEDIUM,
  '分析慢查询并优化索引',
  dev2.id
)

const task3 = taskManager.createTask(
  admin,
  '制定代码规范文档',
  Priority.LOW,
  '建立团队统一的代码规范'
)

// 更新任务状态
console.log('\n=== 更新任务状态 ===')
taskManager.updateTaskStatus(dev1, task1.id, TaskStatus.IN_PROGRESS)
taskManager.updateTaskStatus(dev2, task2.id, TaskStatus.IN_PROGRESS)
taskManager.updateTaskStatus(admin, task3.id, TaskStatus.DONE)

// 查询任务
console.log('\n=== 查询任务 ===')
console.log('进行中的任务:', taskManager.getTasksByStatus(TaskStatus.IN_PROGRESS))
console.log('开发者1的任务:', taskManager.getTasksByAssignee(dev1.id))
console.log('任务统计:', taskManager.getTaskStatistics())

// 权限测试
console.log('\n=== 权限测试 ===')
try {
  // 开发者尝试删除不属于自己的任务(应该失败)
  taskManager.deleteTask(dev1, task2.id)
} catch (error) {
  console.log('权限验证成功:', error.message)
}

// 管理员删除任务(应该成功)
taskManager.deleteTask(admin, task3.id)

7.8 系统特性总结

这个任务管理系统展示了以下 TypeScript 特性的综合应用:

1. 类与继承:

  • UserTask 类封装了业务逻辑
  • 清晰的属性访问控制(public、private、readonly)

2. 接口设计:

  • IUserITaskIPermissionChecker 定义了清晰的契约
  • 接口实现确保了类型安全

3. 枚举应用:

  • TaskStatusPriorityUserRole 提供了类型安全的常量
  • 避免了魔法字符串和数字

4. 装饰器功能:

  • @log 提供了方法调用日志
  • @requirePermission 实现了权限控制
  • @cache 提供了缓存功能
  • @validate 实现了参数验证

5. 模块化设计:

  • 清晰的职责分离
  • 易于测试和维护
  • 可扩展的架构

6. 类型安全:

  • 编译时类型检查
  • 防止运行时错误
  • 良好的IDE支持

这个系统演示了如何在实际项目中合理运用 TypeScript 的各种特性,创建出既类型安全又易于维护的企业级应用。

8. 学习总结 📚

通过第三天的深度学习,我们掌握了 TypeScript 的核心面向对象特性:

🏗️ 类系统核心概念:

  • 基础类定义:属性、方法、构造函数的使用
  • 访问修饰符:public、private、protected 的正确使用
  • 静态成员:类级别的属性和方法
  • 参数属性:构造函数参数的简化写法
  • 继承与多态:代码复用和接口统一的实现
  • 抽象类:为相关类提供共同基础结构
  • 接口实现:定义类必须遵循的契约
  • 属性访问器:getter/setter 的使用场景

📦 模块系统精髓:

  • ES6 模块:import/export 的各种用法
  • 重新导出:创建统一的模块入口点
  • 动态导入:按需加载和代码分割
  • 模块解析:相对导入 vs 非相对导入
  • 路径映射:简化导入路径的配置
  • 命名空间:全局代码组织的传统方式

✨ 装饰器的威力:

  • 方法装饰器:增强方法功能(日志、缓存、重试等)
  • 类装饰器:修改类的行为(单例、自动绑定等)
  • 属性装饰器:控制属性访问(验证、格式化等)
  • 参数装饰器:参数级别的验证和处理
  • 装饰器工厂:可配置的装饰器创建
  • 装饰器组合:多个装饰器的协同工作

🎯 枚举的艺术:

  • 数字枚举:自动递增的数字常量
  • 字符串枚举:更好的调试体验
  • const 枚举:编译时优化
  • 计算成员:动态计算的枚举值
  • 枚举的最佳实践:何时使用枚举 vs 联合类型

🔗 声明合并与扩展:

  • 接口合并:多个同名接口的自动合并
  • 模块扩展:扩展现有模块的功能
  • 全局扩展:为内置对象添加新方法

🎭 混入模式:

  • 多重继承的实现:组合多个类的功能
  • 类型约束:确保混入的类型安全

通过第三天的深入学习,我们已经掌握了 TypeScript 面向对象编程的核心概念和实际应用。这些知识将为构建大型、可维护的 TypeScript 应用程序奠定坚实的基础。