抽象类

抽象类是不能被直接实例化的类,它作为其他类的基类使用。抽象类可以包含抽象方法,子类必须实现这些方法。抽象类是定义通用接口和共享实现的理想方式。

定义抽象类

使用 abstract 关键字定义抽象类。抽象类不能使用 new 直接实例化。

abstract class Animal {
  name: string

  constructor(name: string) {
    this.name = name
  }

  abstract speak(): void

  move(): void {
    console.log(`${this.name} 移动`)
  }
}

class Dog extends Animal {
  speak(): void {
    console.log(`${this.name} 汪汪叫`)
  }
}

const dog = new Dog("小黑")
dog.speak()
dog.move()

Animal 是抽象类,定义了抽象方法 speak 和具体方法 moveDog 继承 Animal 并实现了 speak 方法。尝试直接创建 Animal 实例会报错。

抽象方法

抽象方法只有签名没有实现,子类必须提供具体实现。

abstract class Shape {
  abstract getArea(): number
  abstract getPerimeter(): number

  describe(): string {
    return `面积: ${this.getArea()}, 周长: ${this.getPerimeter()}`
  }
}

class Rectangle extends Shape {
  constructor(
    public width: number,
    public height: number
  ) {
    super()
  }

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

  getPerimeter(): number {
    return 2 * (this.width + this.height)
  }
}

class Circle extends Shape {
  constructor(public radius: number) {
    super()
  }

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

  getPerimeter(): number {
    return 2 * Math.PI * this.radius
  }
}

const rect = new Rectangle(10, 20)
const circle = new Circle(5)

console.log(rect.describe())
console.log(circle.describe())

Shape 定义了两个抽象方法,子类 RectangleCircle 各自实现了计算逻辑。describe 方法在抽象类中提供通用实现,子类可以直接使用。

抽象属性

抽象类可以定义抽象属性,子类必须实现这些属性。

abstract class Entity {
  abstract id: number
  abstract createdAt: Date

  getAge(): number {
    return Date.now() - this.createdAt.getTime()
  }
}

class User extends Entity {
  id: number
  createdAt: Date
  name: string

  constructor(id: number, name: string) {
    super()
    this.id = id
    this.name = name
    this.createdAt = new Date()
  }
}

const user = new User(1, "张三")
console.log(user.id)
console.log(user.name)
console.log(user.getAge())

idcreatedAt 是抽象属性,User 类必须定义这些属性。

抽象类与接口的区别

抽象类和接口都可以定义契约,但有一些重要区别。

interface IAnimal {
  name: string
  speak(): void
}

abstract class AnimalBase {
  name: string

  constructor(name: string) {
    this.name = name
  }

  abstract speak(): void

  move(): void {
    console.log(`${this.name} 移动`)
  }
}

class Cat1 implements IAnimal {
  name: string

  constructor(name: string) {
    this.name = name
  }

  speak(): void {
    console.log(`${this.name} 喵喵叫`)
  }
}

class Cat2 extends AnimalBase {
  constructor(name: string) {
    super(name)
  }

  speak(): void {
    console.log(`${this.name} 喵喵叫`)
  }
}

主要区别:

  • 抽象类可以有构造函数和具体实现,接口不能
  • 抽象类可以有访问修饰符,接口成员默认是 public
  • 类可以继承一个抽象类,但可以实现多个接口
  • 抽象类适合定义"是什么",接口适合定义"能做什么"

模板方法模式

抽象类常用于实现模板方法模式,在抽象类中定义算法骨架,子类实现具体步骤。

abstract class DataParser {
  parse(filePath: string): any {
    const content = this.readFile(filePath)
    const data = this.parseContent(content)
    const validated = this.validate(data)
    return this.transform(validated)
  }

  protected readFile(filePath: string): string {
    console.log(`读取文件: ${filePath}`)
    return "文件内容"
  }

  protected abstract parseContent(content: string): any

  protected validate(data: any): any {
    console.log("验证数据")
    return data
  }

  protected transform(data: any): any {
    console.log("转换数据")
    return data
  }
}

class JsonParser extends DataParser {
  protected parseContent(content: string): any {
    console.log("解析 JSON")
    return JSON.parse(content || "{}")
  }
}

class XmlParser extends DataParser {
  protected parseContent(content: string): any {
    console.log("解析 XML")
    return { parsed: "xml" }
  }
}

const jsonParser = new JsonParser()
const xmlParser = new XmlParser()

jsonParser.parse("data.json")
xmlParser.parse("data.xml")

parse 方法定义了处理流程,子类只需要实现 parseContent 方法。这种模式让代码结构清晰,也便于扩展新的解析器。

抽象类作为类型

抽象类可以作为类型使用,用于标注变量的类型。

abstract class Component {
  abstract render(): string
}

class Button extends Component {
  render(): string {
    return "<button>点击</button>"
  }
}

class Input extends Component {
  render(): string {
    return "<input type='text' />"
  }
}

function renderPage(components: Component[]): string {
  return components.map(c => c.render()).join("\n")
}

const page = renderPage([
  new Button(),
  new Input(),
  new Button()
])

console.log(page)

Component 作为类型标注参数,任何继承 Component 的类都可以传入。

抽象类继承抽象类

抽象类可以继承另一个抽象类,可以保持或实现父类的抽象成员。

abstract class Vehicle {
  abstract move(): void
}

abstract class MotorVehicle extends Vehicle {
  engineRunning: boolean = false

  startEngine(): void {
    this.engineRunning = true
    console.log("引擎启动")
  }

  stopEngine(): void {
    this.engineRunning = false
    console.log("引擎停止")
  }
}

class Car extends MotorVehicle {
  move(): void {
    if (!this.engineRunning) {
      this.startEngine()
    }
    console.log("汽车行驶")
  }
}

const car = new Car()
car.move()
car.stopEngine()

MotorVehicle 继承 Vehicle 但没有实现 move 方法,仍然是抽象类。Car 继承 MotorVehicle 并实现了 move 方法。

实际应用场景

抽象类在实际开发中有很多应用场景,比如定义基础控制器:

abstract class BaseController {
  protected abstract getModel(): string

  async index(): Promise<string> {
    return `获取 ${this.getModel()} 列表`
  }

  async show(id: number): Promise<string> {
    return `获取 ${this.getModel()} ID: ${id}`
  }

  async store(data: any): Promise<string> {
    console.log("验证数据:", data)
    return `创建 ${this.getModel()}`
  }

  async update(id: number, data: any): Promise<string> {
    console.log("验证数据:", data)
    return `更新 ${this.getModel()} ID: ${id}`
  }

  async destroy(id: number): Promise<string> {
    return `删除 ${this.getModel()} ID: ${id}`
  }
}

class UserController extends BaseController {
  protected getModel(): string {
    return "用户"
  }
}

const userController = new UserController()
console.log(userController.index())
console.log(userController.show(1))
console.log(userController.store({ name: "张三" }))

基础控制器定义了通用的 CRUD 操作,子类只需要指定模型名称。

小结

抽象类是不能实例化的类,用于定义子类的通用模板。它可以包含抽象方法和具体实现,子类必须实现所有抽象成员。抽象类适合定义"是什么"的关系,提供代码复用和类型约束。合理使用抽象类可以构建清晰的类层次结构,提高代码的可维护性。