接口不仅能描述普通对象的形状,还能定义类的结构契约。通过类类型接口,我们可以明确规定一个类必须具备哪些属性和方法,这在团队协作和大型项目中尤为重要。
当一个类实现接口时,必须保证类中包含接口定义的所有成员。这种强制约束让代码更加可靠,也便于不同开发者之间的协作。
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 接口。接口要求类必须有 name、age 属性和 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 关键字,类必须遵循接口定义的契约,确保代码的一致性和可维护性。在实际项目中,合理使用类类型接口可以让代码结构更加清晰,也便于团队协作和后期维护。