类类型接口

接口不仅能描述普通对象的形状,还能定义类的结构契约。通过类类型接口,我们可以明确规定一个类必须具备哪些属性和方法,这在团队协作和大型项目中尤为重要。

接口定义类结构

当一个类实现接口时,必须保证类中包含接口定义的所有成员。这种强制约束让代码更加可靠,也便于不同开发者之间的协作。

interface Animal {
  name: string
  age: number
  speak(): void
}

class Dog implements Animal {
  name: string
  age: number

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }

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

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

上面的代码中,Dog 类实现了 Animal 接口。接口要求类必须有 nameage 属性和 speak 方法,少一个都会报错。

接口只检查公共成员

接口定义的是类的公共契约,只检查类的公共成员。私有属性和受保护属性不在接口检查范围内。

interface Counter {
  count: number
  increment(): void
}

class MyCounter implements Counter {
  count = 0
  private secret = "内部数据"

  increment(): void {
    this.count++
  }
}

const counter = new MyCounter()
counter.increment()
console.log(counter.count)

secret 是私有属性,接口不会检查它是否存在。接口只关心公共接口是否符合约定。

一个类实现多个接口

类可以同时实现多个接口,用逗号分隔。这种设计让类的职责更加清晰,也便于代码的复用。

interface Flyable {
  fly(): void
}

interface Swimmable {
  swim(): void
}

class Duck implements Flyable, Swimmable {
  fly(): void {
    console.log("鸭子飞翔")
  }

  swim(): void {
    console.log("鸭子游泳")
  }
}

const duck = new Duck()
duck.fly()
duck.swim()

鸭子既能飞又能游泳,通过实现两个接口来描述这两种能力。如果后续需要添加新的能力,只需定义新接口并实现即可。

接口继承类

接口可以继承类,这会继承类的所有成员,包括私有和受保护成员。当你需要创建一个与现有类结构相同但不包含实现的接口时,这个特性很有用。

class Person {
  name: string
  private age: number

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
}

interface IEmployee extends Person {
  employeeId: string
}

class Manager implements IEmployee {
  name: string
  private age: number
  employeeId: string

  constructor(name: string, age: number, employeeId: string) {
    this.name = name
    this.age = age
    this.employeeId = employeeId
  }
}

IEmployee 继承了 Person 类的结构,所以实现 IEmployee 的类必须包含 Person 的所有成员,包括私有属性 age

构造器签名

接口可以定义构造器签名,用于描述可以实例化的类。这种写法常用于工厂模式或依赖注入场景。

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

interface AnimalConstructor {
  new (name: string): IAnimal
}

function createAnimal(ctor: AnimalConstructor, name: string): IAnimal {
  return new ctor(name)
}

class Cat implements IAnimal {
  name: string

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

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

const cat = createAnimal(Cat, "小白")
cat.speak()

AnimalConstructor 接口定义了一个构造器签名,要求传入的类必须能用 new 调用并返回 IAnimal 实例。

类静态部分与实例部分

类有两个类型:静态部分的类型和实例部分的类型。接口只能描述类的实例部分,静态成员需要单独定义。

interface ClockInterface {
  currentTime: Date
  setTime(d: Date): void
}

interface ClockConstructor {
  new (hour: number, minute: number): ClockInterface
}

class Clock implements ClockInterface {
  currentTime: Date = new Date()

  setTime(d: Date): void {
    this.currentTime = d
  }

  constructor(h: number, m: number) {
    this.currentTime = new Date()
    this.currentTime.setHours(h, m)
  }
}

function createClock(ctor: ClockConstructor, h: number, m: number): ClockInterface {
  return new ctor(h, m)
}

const clock = createClock(Clock, 12, 30)
console.log(clock.currentTime)

ClockInterface 描述实例成员,ClockConstructor 描述构造器。通过这种分离,可以更精确地控制类的类型。

实战场景

类类型接口在实际开发中有很多应用场景。比如定义服务层的统一接口:

interface IUserService {
  getUser(id: number): Promise<User>
  createUser(user: User): Promise<User>
  updateUser(id: number, user: Partial<User>): Promise<User>
  deleteUser(id: number): Promise<void>
}

class UserService implements IUserService {
  async getUser(id: number): Promise<User> {
    return { id, name: "张三", email: "zhangsan@example.com" }
  }

  async createUser(user: User): Promise<User> {
    console.log("创建用户:", user)
    return user
  }

  async updateUser(id: number, user: Partial<User>): Promise<User> {
    return { id, ...user } as User
  }

  async deleteUser(id: number): Promise<void> {
    console.log("删除用户:", id)
  }
}

interface User {
  id: number
  name: string
  email: string
}

这样定义后,任何实现 IUserService 的类都必须提供完整的 CRUD 方法,便于替换具体实现和编写测试。

小结

类类型接口为面向对象编程提供了强有力的类型保障。通过 implements 关键字,类必须遵循接口定义的契约,确保代码的一致性和可维护性。在实际项目中,合理使用类类型接口可以让代码结构更加清晰,也便于团队协作和后期维护。