Skip to content

类与接口对比

类和接口是 TypeScript 中两种重要的类型定义方式。理解它们的区别和配合使用方式,对于编写健壮的 TypeScript 代码至关重要。

implements 回顾#

类使用 implements 实现接口的契约:

// TypeScript 5.x
interface Printable {
print(): void
}
interface Loggable {
log(message: string): void
}
// 实现多个接口
class Document implements Printable, Loggable {
constructor(private content: string) {}
print(): void {
console.log(this.content)
}
log(message: string): void {
console.log(`[Document] ${message}`)
}
}
const doc = new Document('Hello, World!')
doc.print()
doc.log('Created')

类作为类型#

类声明会创建两个东西:

  1. 构造函数:用于创建实例
  2. 类型:描述实例的结构
class Point {
constructor(
public x: number,
public y: number
) {}
distanceTo(other: Point): number {
return Math.sqrt((this.x - other.x) ** 2 + (this.y - other.y) ** 2)
}
}
// Point 作为类型使用
function printPoint(p: Point): void {
console.log(`(${p.x}, ${p.y})`)
}
// 任何满足 Point 结构的对象都可以
const point: Point = new Point(1, 2)
const fakePoint = { x: 3, y: 4, distanceTo: (other: Point) => 0 }
printPoint(point) // ✅
printPoint(fakePoint) // ✅ 结构兼容

类型 vs 值#

class User {
constructor(
public name: string,
public age: number
) {}
}
// 作为类型:描述实例形状
const user: User = new User('张三', 25)
// 作为值:构造函数本身
const UserClass = User
const user2 = new UserClass('李四', 30)
// typeof 获取构造函数类型
type UserConstructor = typeof User
// new (name: string, age: number) => User
function createUser(ctor: UserConstructor, name: string, age: number): User {
return new ctor(name, age)
}
const user3 = createUser(User, '王五', 35)

接口描述类的形状#

接口可以描述类实例的形状:

interface ClockInterface {
currentTime: Date
setTime(d: Date): void
}
class Clock implements ClockInterface {
currentTime: Date = new Date()
setTime(d: Date): void {
this.currentTime = d
}
}

接口描述构造函数#

// 描述实例
interface ClockInterface {
tick(): void
}
// 描述构造函数
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface
}
// 工厂函数使用构造函数接口
function createClock(
Ctor: ClockConstructor,
hour: number,
minute: number
): ClockInterface {
return new Ctor(hour, minute)
}
class DigitalClock implements ClockInterface {
constructor(h: number, m: number) {
// 初始化
}
tick(): void {
console.log('beep beep')
}
}
class AnalogClock implements ClockInterface {
constructor(h: number, m: number) {
// 初始化
}
tick(): void {
console.log('tick tock')
}
}
const digital = createClock(DigitalClock, 12, 30)
const analog = createClock(AnalogClock, 7, 45)

类表达式#

// 命名类表达式
const Rectangle = class RectangleClass {
constructor(
public width: number,
public height: number
) {}
getArea(): number {
return this.width * this.height
}
}
// 匿名类表达式
const Square = class {
constructor(public size: number) {}
getArea(): number {
return this.size * this.size
}
}
// 实现接口的类表达式
interface Shape {
getArea(): number
}
const Circle: new (radius: number) => Shape = class {
constructor(public radius: number) {}
getArea(): number {
return Math.PI * this.radius ** 2
}
}

接口 vs 抽象类#

特性接口抽象类
实现无(纯类型)可以有部分实现
多继承支持多实现只能单继承
字段无初始值可以有初始值
构造函数可以有
访问修饰符支持
// 接口:纯契约
interface Flyable {
fly(): void
altitude: number
}
// 抽象类:可以有实现
abstract class Aircraft {
abstract fly(): void
protected speed: number = 0
accelerate(amount: number): void {
this.speed += amount
console.log(`Speed: ${this.speed}`)
}
}
// 结合使用
class Airplane extends Aircraft implements Flyable {
altitude: number = 0
fly(): void {
console.log('Airplane flying')
}
}

混入模式(Mixins)#

TypeScript 可以通过混入实现类似多继承的效果:

// 混入类型
type Constructor<T = {}> = new (...args: any[]) => T
// 时间戳混入
function Timestamped<TBase extends Constructor>(Base: TBase) {
return class extends Base {
createdAt = new Date()
updatedAt = new Date()
touch() {
this.updatedAt = new Date()
}
}
}
// 标签混入
function Tagged<TBase extends Constructor>(Base: TBase) {
return class extends Base {
tags: string[] = []
addTag(tag: string) {
this.tags.push(tag)
}
}
}
// 基类
class User {
constructor(public name: string) {}
}
// 应用混入
const TaggedTimestampedUser = Tagged(Timestamped(User))
const user = new TaggedTimestampedUser('张三')
user.addTag('admin')
user.touch()
console.log(user.name) // 张三
console.log(user.tags) // ['admin']
console.log(user.createdAt) // Date

依赖注入模式#

// 服务接口
interface Logger {
log(message: string): void
}
interface Database {
query(sql: string): Promise<any[]>
}
// 实现
class ConsoleLogger implements Logger {
log(message: string): void {
console.log(message)
}
}
class MySQLDatabase implements Database {
async query(sql: string): Promise<any[]> {
console.log(`Executing: ${sql}`)
return []
}
}
// 使用依赖注入
class UserService {
constructor(
private logger: Logger,
private database: Database
) {}
async getUsers(): Promise<any[]> {
this.logger.log('Fetching users...')
return this.database.query('SELECT * FROM users')
}
}
// 组装
const logger = new ConsoleLogger()
const database = new MySQLDatabase()
const userService = new UserService(logger, database)

工厂模式#

interface Product {
name: string
price: number
describe(): string
}
interface ProductFactory {
create(name: string, price: number): Product
}
class Book implements Product {
constructor(
public name: string,
public price: number,
public author: string
) {}
describe(): string {
return `${this.name}》 by ${this.author} - ¥${this.price}`
}
}
class Electronics implements Product {
constructor(
public name: string,
public price: number,
public warranty: number
) {}
describe(): string {
return `${this.name} - ¥${this.price} (${this.warranty}年保修)`
}
}
class BookFactory implements ProductFactory {
constructor(private defaultAuthor: string) {}
create(name: string, price: number): Book {
return new Book(name, price, this.defaultAuthor)
}
}
class ElectronicsFactory implements ProductFactory {
constructor(private defaultWarranty: number) {}
create(name: string, price: number): Electronics {
return new Electronics(name, price, this.defaultWarranty)
}
}
// 使用
const bookFactory = new BookFactory('未知作者')
const electronicsFactory = new ElectronicsFactory(1)
const book = bookFactory.create('TypeScript 指南', 99)
const phone = electronicsFactory.create('智能手机', 4999)
console.log(book.describe())
console.log(phone.describe())

实际应用:服务层设计#

// 仓储接口
interface Repository<T> {
findById(id: string): Promise<T | null>
findAll(): Promise<T[]>
save(entity: T): Promise<T>
delete(id: string): Promise<void>
}
// 实体
interface User {
id: string
name: string
email: string
}
// 服务接口
interface UserService {
getUser(id: string): Promise<User | null>
getAllUsers(): Promise<User[]>
createUser(data: Omit<User, 'id'>): Promise<User>
deleteUser(id: string): Promise<void>
}
// 仓储实现
class InMemoryUserRepository implements Repository<User> {
private users: Map<string, User> = new Map()
async findById(id: string): Promise<User | null> {
return this.users.get(id) || null
}
async findAll(): Promise<User[]> {
return Array.from(this.users.values())
}
async save(user: User): Promise<User> {
this.users.set(user.id, user)
return user
}
async delete(id: string): Promise<void> {
this.users.delete(id)
}
}
// 服务实现
class UserServiceImpl implements UserService {
constructor(private repository: Repository<User>) {}
async getUser(id: string): Promise<User | null> {
return this.repository.findById(id)
}
async getAllUsers(): Promise<User[]> {
return this.repository.findAll()
}
async createUser(data: Omit<User, 'id'>): Promise<User> {
const user: User = {
id: Math.random().toString(36).slice(2),
...data,
}
return this.repository.save(user)
}
async deleteUser(id: string): Promise<void> {
return this.repository.delete(id)
}
}
// 使用
const repository = new InMemoryUserRepository()
const service = new UserServiceImpl(repository)
async function main() {
const user = await service.createUser({
name: '张三',
email: 'zhangsan@example.com',
})
console.log('Created:', user)
const users = await service.getAllUsers()
console.log('All users:', users)
}

常见问题#

🙋 何时用接口,何时用类?#

🙋 类实现接口时,接口的属性会成为类的属性吗?#

不会自动成为,需要手动声明:

interface Named {
name: string
}
// ❌ 错误:缺少 name 属性
// class Person implements Named { }
// ✅ 必须手动声明
class Person implements Named {
name: string = ''
}

🙋 如何检查一个对象是否实现了某个接口?#

TypeScript 的接口是编译时概念,运行时不存在。需要用类型守卫:

interface Flyable {
fly(): void
}
function isFlyable(obj: unknown): obj is Flyable {
return typeof obj === 'object' && obj !== null && 'fly' in obj
}
if (isFlyable(someObject)) {
someObject.fly()
}

总结#

使用场景选择
描述数据结构接口
定义服务契约接口
需要实例化
需要部分实现抽象类
依赖注入接口 + 类
工厂模式接口 + 类

下一篇我们将进入泛型的学习,掌握 TypeScript 中最强大的类型工具之一。