理解单一职责原则:通过接口分离职责(基于TypeScript严格模式)

在SOLID五大原则中,单一职责原则(SRP) 是最基础却也最容易被误解的一个。很多开发者认为SRP就是"一个类只有一个方法",实则不然。真正的SRP是:一个类应该只有一个引起它变化的原因

今天,我们将深入探讨如何通过接口定义实现职责分离,这是SRP实践中最优雅且类型安全的方式,尤其在TypeScript严格模式下。


一、为什么接口是职责分离的最佳工具?

接口的本质是契约定义。当我们将大职责拆分成多个小接口时,实际上是在定义多个独立的契约。实现类可以选择性地签署这些契约,从而避免承担不必要的职责。

核心优势

  1. 编译期校验:TypeScript严格模式会在编译期强制检查接口实现

  2. 依赖精准:依赖方只需知道自己需要的接口,无需关心实现细节

  3. 测试友好:可以轻松mock单个接口行为

  4. 扩展灵活:新增接口不影响现有实现


二、案例一:文件处理系统(入门)

❌ 违反SRP:一个类处理所有文件操作

TypeScript 复制代码
class FileHelper {
  // 职责1:读取文件
  readFile(path: string): string {
    if (!this.exists(path)) throw new Error('File not found');
    return 'file content';
  }

  // 职责2:写入文件
  writeFile(path: string, content: string): void {
    // 写入逻辑
  }

  // 职责3:压缩文件
  compress(path: string): void {
    // 压缩逻辑
  }

  // 职责4:上传文件
  upload(path: string, url: string): Promise<void> {
    return fetch(url, { method: 'POST', body: path }).then();
  }

  // 职责5:删除文件
  delete(path: string): void {
    // 删除逻辑
  }

  // 职责6:检查文件是否存在
  private exists(path: string): boolean {
    return true;
  }
}

问题分析

  • 修改压缩算法 → 影响上传功能

  • 更换存储方式(如从本地到云端) → 必须修改整个类

  • 单元测试需要mock所有方法,哪怕只测试读取功能

✅ 遵守SRP:通过接口分离

TypeScript 复制代码
// ============= 接口定义:每个接口一个职责 =============

// 职责契约1:可读
interface IReadable {
  read(path: string): string;
}

// 职责契约2:可写
interface IWritable {
  write(path: string, content: string): void;
}

// 职责契约3:可压缩
interface ICompressible {
  compress(path: string): void;
}

// 职责契约4:可上传
interface IUploadable {
  upload(path: string, url: string): Promise<void>;
}

// 职责契约5:可删除
interface IDeletable {
  delete(path: string): void;
}

// 职责契约6:可检查存在性
interface IExistenceCheckable {
  exists(path: string): boolean;
}

// ============= 实现类:按需签署契约 =============

// 实现类1:本地文件系统(只读、写、删除、检查)
class LocalFileHandler implements IReadable, IWritable, IDeletable, IExistenceCheckable {
  read(path: string): string {
    if (!this.exists(path)) throw new Error('File not found');
    return 'local file content';
  }

  write(path: string, content: string): void {
    console.log(`Writing to local: ${path}`);
  }

  delete(path: string): void {
    console.log(`Deleting local: ${path}`);
  }

  exists(path: string): boolean {
    return true;
  }
}

// 实现类2:云存储文件(可读、可写、可上传、可删除)
class CloudFileHandler implements IReadable, IWritable, IUploadable, IDeletable {
  constructor(
    private readonly bucket: string, // 严格模式:readonly防止修改
    private readonly apiKey: string
  ) {
    if (!bucket || !apiKey) throw new Error('Cloud credentials required');
  }

  read(path: string): string {
    return 'cloud file content';
  }

  write(path: string, content: string): void {
    console.log(`Writing to cloud bucket ${this.bucket}: ${path}`);
  }

  async upload(path: string, url: string): Promise<void> {
    // 严格模式:必须处理Promise拒绝
    try {
      await fetch(url, { method: 'POST', body: path });
    } catch (error) {
      throw new Error(`Upload failed: ${(error as Error).message}`);
    }
  }

  delete(path: string): void {
    console.log(`Deleting cloud: ${path}`);
  }
}

// 实现类3:只读文件处理器(仅实现读取)
class ReadOnlyFileHandler implements IReadable {
  read(path: string): string {
    return 'read-only content';
  }
}

// ============= 使用方:只依赖需要的接口 =============

class DataProcessor {
  // 只依赖IReadable,不关心实现
  constructor(private readonly fileReader: IReadable) {}

  process(path: string): string {
    const content = this.fileReader.read(path);
    return content.toUpperCase();
  }
}

// ============= 测试:轻松mock =============

test('DataProcessor should process file content', () => {
  // 只mock IReadable接口
  const mockReader: IReadable = {
    read: jest.fn().mockReturnValue('test content')
  };

  const processor = new DataProcessor(mockReader);
  const result = processor.process('test.txt');

  expect(result).toBe('TEST CONTENT');
  expect(mockReader.read).toHaveBeenCalledWith('test.txt');
});

核心优势

  • DataProcessor只依赖IReadable,完全不知道LocalFileHandlerCloudFileHandler的存在

  • 测试时只需mock单个接口方法

  • 新增文件处理方式只需实现对应接口,无需修改现有代码


三、案例二:订单处理系统(进阶)

❌ 违反SRP:订单类承担所有职责

TypeScript 复制代码
class Order {
  constructor(
    public items: OrderItem[],
    public customer: Customer,
    public status: 'pending' | 'paid' | 'shipped'
  ) {}

  // 职责1:计算总价
  calculateTotal(): number {
    return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  }

  // 职责2:应用折扣
  applyDiscount(discountCode: string): void {
    const discount = this.validateDiscount(discountCode);
    this.items.forEach(item => {
      item.price *= (1 - discount.percentage / 100);
    });
  }

  // 职责3:验证折扣码
  private validateDiscount(code: string): Discount {
    // 查询数据库验证
    return { code, percentage: 10, valid: true };
  }

  // 职责4:保存订单
  async save(): Promise<void> {
    await db.insert('orders', this);
  }

  // 职责5:发送确认邮件
  async sendConfirmation(): Promise<void> {
    const email = this.buildEmail();
    await mailer.send(this.customer.email, email);
  }

  // 职责6:构建邮件内容
  private buildEmail(): string {
    return `Order ${this.customer.id} confirmed`;
  }

  // 职责7:检查库存
  async checkInventory(): Promise<boolean> {
    for (const item of this.items) {
      const stock = await inventory.get(item.productId);
      if (stock < item.quantity) return false;
    }
    return true;
  }
}

问题分析

  • 修改邮件模板 → 影响库存检查逻辑

  • 更换数据库 → 所有功能都需要测试

  • 无法单独重用"计算总价"逻辑

✅ 遵守SRP:接口分离 + 策略模式

TypeScript 复制代码
// ============= 接口定义:每个职责一个契约 =============

// 职责1:可计算价格
interface IPriceCalculable {
  calculateTotal(): number;
  applyDiscount(discount: IDiscountStrategy): void;
}

// 职责2:可验证折扣
interface IDiscountValidatable {
  validateDiscount(code: string): DiscountInfo;
}

// 职责3:可持久化
interface IPersistable<T> {
  save(entity: T): Promise<void>;
  findById(id: number): Promise<T | null>;
}

// 职责4:可通知
interface INotifiable {
  sendConfirmation(): Promise<void>;
}

// 职责5:可检查库存
interface IInventoryCheckable {
  checkInventory(): Promise<boolean>;
}

// 职责6:折扣策略(独立的职责单元)
interface IDiscountStrategy {
  apply(items: OrderItem[]): number; // 返回折扣后的总价
}

// ============= 实现类:职责独立 =============

// 实现1:简单折扣策略
class PercentageDiscountStrategy implements IDiscountStrategy {
  constructor(private readonly percentage: number) {
    if (percentage < 0 || percentage > 100) {
      throw new Error('Discount percentage must be between 0 and 100');
    }
  }

  apply(items: OrderItem[]): number {
    const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
    return total * (1 - this.percentage / 100);
  }
}

// 实现2:会员折扣策略
class MemberDiscountStrategy implements IDiscountStrategy {
  constructor(private readonly memberLevel: 'gold' | 'silver' | 'bronze') {}

  apply(items: OrderItem[]): number {
    const discounts = { gold: 0.2, silver: 0.1, bronze: 0.05 };
    const rate = discounts[this.memberLevel];
    const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
    return total * (1 - rate);
  }
}

// 实现3:订单实体(只保留数据)
class Order {
  constructor(
    public readonly items: readonly OrderItem[], // 严格模式:只读保护数据
    public readonly customer: Customer,
    private _status: 'pending' | 'paid' | 'shipped'
  ) {}

  get status() { return this._status; } // 严格模式:私有状态通过getter暴露

  set status(value: 'pending' | 'paid' | 'shipped') {
    this._status = value;
  }
}

// 实现4:订单价格计算器
class OrderPriceCalculator implements IPriceCalculable {
  constructor(private readonly order: Order) {} // 组合优于继承

  calculateTotal(): number {
    return this.order.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  }

  applyDiscount(strategy: IDiscountStrategy): number {
    if (this.order.status !== 'pending') {
      throw new Error('Can only apply discount to pending orders');
    }
    return strategy.apply([...this.order.items]); // 严格模式:复制数组避免副作用
  }
}

// 实现5:订单仓库(持久化)
class OrderRepository implements IPersistable<Order> {
  constructor(private readonly dbConnection: Database) {} // 依赖注入

  async save(order: Order): Promise<void> {
    // 严格模式:必须处理可能的null/undefined
    if (!order) throw new Error('Order cannot be null');
    await this.dbConnection.insert('orders', order);
  }

  async findById(id: number): Promise<Order | null> {
    const data = await this.dbConnection.query('SELECT * FROM orders WHERE id = ?', [id]);
    return data.length > 0 ? new Order(data[0].items, data[0].customer, data[0].status) : null;
  }
}

// 实现6:订单通知服务
class OrderNotificationService implements INotifiable {
  constructor(
    private readonly emailService: IEmailService,
    private readonly templateEngine: ITemplateEngine
  ) {}

  async sendConfirmation(order: Order): Promise<void> {
    const email = this.buildEmail(order);
    await this.emailService.send(order.customer.email, email);
  }

  private buildEmail(order: Order): string {
    const template = this.templateEngine.render('order-confirmation', {
      orderId: order.customer.id,
      total: new OrderPriceCalculator(order).calculateTotal()
    });
    return template;
  }
}

// 实现7:库存服务
class InventoryService implements IInventoryCheckable {
  constructor(private readonly inventoryApi: IInventoryApi) {}

  async checkInventory(order: Order): Promise<boolean> {
    // 严格模式:Promise.all需要类型断言
    const results = await Promise.all(
      order.items.map(item => this.checkItem(item))
    );
    return results.every(result => result === true); // 严格模式:必须明确比较
  }

  private async checkItem(item: OrderItem): Promise<boolean> {
    const stock = await this.inventoryApi.getStock(item.productId);
    return stock >= item.quantity;
  }
}

// ============= 使用方:组合职责 =============

class OrderProcessor {
  constructor(
    private readonly order: Order,
    private readonly calculator: OrderPriceCalculator,
    private readonly repository: OrderRepository,
    private readonly notifier: OrderNotificationService,
    private readonly inventoryService: InventoryService
  ) {}

  async process(discountCode?: string): Promise<void> {
    // 1. 检查库存
    const inStock = await this.inventoryService.checkInventory(this.order);
    if (!inStock) throw new Error('Insufficient inventory');

    // 2. 应用折扣
    if (discountCode) {
      // 可以动态替换折扣策略
      const strategy = new PercentageDiscountStrategy(10);
      this.calculator.applyDiscount(strategy);
    }

    // 3. 保存订单
    await this.repository.save(this.order);

    // 4. 发送通知
    await this.notifier.sendConfirmation(this.order);
  }
}

// ============= 测试:独立测试每个职责 =============

test('OrderPriceCalculator applies discount correctly', () => {
  const order = new Order(
    [{ productId: 1, price: 100, quantity: 1 }],
    { id: 1, email: 'test@example.com' },
    'pending'
  );
  
  const calculator = new OrderPriceCalculator(order);
  const strategy = new PercentageDiscountStrategy(10);
  
  const discounted = calculator.applyDiscount(strategy);
  expect(discounted).toBe(90);
});

test('InventoryService checks inventory correctly', async () => {
  const mockApi: IInventoryApi = {
    getStock: jest.fn().mockResolvedValue(5)
  };
  
  const service = new InventoryService(mockApi);
  const order = new Order(
    [{ productId: 1, price: 100, quantity: 3 }],
    {} as Customer,
    'pending'
  );
  
  const result = await service.checkInventory(order);
  expect(result).toBe(true);
});

核心优势

  • 每个类都可以独立演化:修改邮件模板不影响库存逻辑

  • 高度可测试:可以单独测试OrderPriceCalculatorInventoryService

  • 灵活组合:可以创建只读订单查看器、只处理折扣的处理器等


四、案例三:用户管理系统(高级)

❌ 违反SRP:上帝类

TypeScript 复制代码
class UserManager {
  // 数据库操作
  async createUser(user: User): Promise<void> {
    // 验证
    if (!this.validateEmail(user.email)) throw new Error('Invalid email');
    // 加密密码
    user.password = await bcrypt.hash(user.password, 10);
    // 保存
    await this.db.query('INSERT INTO users ...', user);
    // 发送欢迎邮件
    await this.sendEmail(user.email, 'Welcome!');
    // 记录日志
    this.log(`User ${user.email} created`);
    // 触发分析事件
    analytics.track('user_created', user);
  }

  // 验证
  validateEmail(email: string): boolean {
    return email.includes('@');
  }

  // 认证
  async login(email: string, password: string): Promise<string> {
    const user = await this.db.query('SELECT * FROM users WHERE email = ?', [email]);
    if (!user) throw new Error('User not found');
    const valid = await bcrypt.compare(password, user.password);
    if (!valid) throw new Error('Invalid password');
    return jwt.sign({ userId: user.id }, 'secret');
  }

  // 发送邮件
  async sendEmail(to: string, content: string): Promise<void> {
    // 邮件逻辑
  }

  // 记录日志
  log(message: string): void {
    console.log(message);
  }

  // 权限检查
  hasPermission(userId: number, permission: string): boolean {
    const user = this.db.query('SELECT * FROM users WHERE id = ?', [userId]);
    return user.role.includes(permission);
  }

  // 生成报表
  async generateUserReport(): Promise<Report> {
    const users = await this.db.query('SELECT * FROM users');
    return { total: users.length, data: users };
  }
}

✅ 遵守SRP:接口拆分 + 服务组合

TypeScript 复制代码
// ============= 接口定义:垂直切片职责 =============

// 数据访问职责
interface IUserRepository {
  create(user: User): Promise<User>;
  findByEmail(email: string): Promise<User | null>;
  findById(id: number): Promise<User | null>;
}

// 验证职责
interface IUserValidator {
  validate(user: User): ValidationResult;
  validateEmail(email: string): boolean;
  validatePassword(password: string): boolean;
}

// 认证职责
interface IAuthenticationService {
  login(email: string, password: string): Promise<string>;
  logout(token: string): Promise<void>;
  verifyToken(token: string): Promise<number>; // 返回userId
}

// 密码安全职责
interface IPasswordService {
  hash(password: string): Promise<string>;
  compare(password: string, hash: string): Promise<boolean>;
}

// 通知职责
interface INotificationService {
  sendWelcomeEmail(user: User): Promise<void>;
  sendPasswordResetEmail(email: string): Promise<void>;
}

// 权限职责
interface IPermissionService {
  hasPermission(userId: number, permission: string): Promise<boolean>;
  assignRole(userId: number, role: string): Promise<void>;
}

// 报表职责
interface IUserReportService {
  generateReport(): Promise<Report>;
  exportReport(format: 'pdf' | 'csv'): Promise<Buffer>;
}

// 审计日志职责
interface IAuditLogger {
  logUserAction(userId: number, action: string, details: Record<string, any>): void;
}

// 分析跟踪职责
interface IAnalyticsService {
  track(event: string, properties: Record<string, any>): void;
  identify(userId: number, traits: Record<string, any>): void;
}

// ============= 实现类:每个类只实现一个职责 =============

// 实现1:用户仓库(仅数据访问)
class UserRepository implements IUserRepository {
  constructor(private readonly db: Database) {} // 依赖注入

  async create(user: User): Promise<User> {
    // 严格模式:必须处理可能的错误
    try {
      const result = await this.db.query('INSERT INTO users ...', user);
      return { ...user, id: result.insertId };
    } catch (error) {
      throw new Error(`Failed to create user: ${(error as Error).message}`);
    }
  }

  async findByEmail(email: string): Promise<User | null> {
    const results = await this.db.query('SELECT * FROM users WHERE email = ?', [email]);
    return results[0] || null; // 严格模式:显式返回null
  }

  async findById(id: number): Promise<User | null> {
    const results = await this.db.query('SELECT * FROM users WHERE id = ?', [id]);
    return results[0] || null;
  }
}

// 实现2:用户验证器(仅验证)
class UserValidator implements IUserValidator {
  private readonly emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; // 严格模式:readonly

  validate(user: User): ValidationResult {
    const errors: string[] = [];
    
    if (!this.validateEmail(user.email)) {
      errors.push('Invalid email format');
    }
    
    if (!this.validatePassword(user.password)) {
      errors.push('Password must be at least 8 characters');
    }

    return {
      isValid: errors.length === 0,
      errors
    };
  }

  validateEmail(email: string): boolean {
    return this.emailRegex.test(email);
  }

  validatePassword(password: string): boolean {
    return password.length >= 8;
  }
}

// 实现3:密码服务(仅密码处理)
class BcryptPasswordService implements IPasswordService {
  private readonly saltRounds = 10; // 严格模式:配置不可变

  async hash(password: string): Promise<string> {
    // 严格模式:空值检查
    if (!password) throw new Error('Password cannot be empty');
    return bcrypt.hash(password, this.saltRounds);
  }

  async compare(password: string, hash: string): Promise<boolean> {
    if (!password || !hash) return false;
    return bcrypt.compare(password, hash);
  }
}

// 实现4:认证服务(仅认证)
class JwtAuthenticationService implements IAuthenticationService {
  constructor(
    private readonly userRepo: IUserRepository,
    private readonly passwordService: IPasswordService,
    private readonly config: { secret: string; expiresIn: string }
  ) {
    // 严格模式:配置校验
    if (!config.secret) throw new Error('JWT secret required');
  }

  async login(email: string, password: string): Promise<string> {
    // 严格模式:必须处理所有null情况
    const user = await this.userRepo.findByEmail(email);
    if (!user) throw new Error('Invalid credentials');

    const isValid = await this.passwordService.compare(password, user.password);
    if (!isValid) throw new Error('Invalid credentials');

    // 严格模式:user.id可能为undefined,需要断言
    return jwt.sign({ userId: user.id! }, this.config.secret, { 
      expiresIn: this.config.expiresIn 
    });
  }

  async logout(token: string): Promise<void> {
    // 添加到黑名单或清除会话
    await this.invalidateToken(token);
  }

  async verifyToken(token: string): Promise<number> {
    try {
      const decoded = jwt.verify(token, this.config.secret) as { userId: number };
      return decoded.userId;
    } catch {
      throw new Error('Invalid token');
    }
  }

  private async invalidateToken(token: string): Promise<void> {
    // 实际注销逻辑
  }
}

// 实现5:通知服务(仅通知)
class EmailNotificationService implements INotificationService {
  constructor(
    private readonly emailService: IEmailService,
    private readonly templateEngine: ITemplateEngine
  ) {}

  async sendWelcomeEmail(user: User): Promise<void> {
    const content = this.templateEngine.render('welcome', { name: user.name });
    await this.emailService.send(user.email, 'Welcome!', content);
  }

  async sendPasswordResetEmail(email: string): Promise<void> {
    const token = this.generateResetToken(email);
    const content = this.templateEngine.render('reset-password', { token });
    await this.emailService.send(email, 'Reset Your Password', content);
  }

  private generateResetToken(email: string): string {
    return crypto.randomBytes(32).toString('hex');
  }
}

// 实现6:用户注册用例(组合所有职责)
class RegisterUserUseCase {
  constructor(
    private readonly validator: IUserValidator,
    private readonly repository: IUserRepository,
    private readonly passwordService: IPasswordService,
    private readonly notificationService: INotificationService,
    private readonly auditLogger: IAuditLogger,
    private readonly analytics: IAnalyticsService
  ) {}

  async execute(userData: UserRegistrationDTO): Promise<Result<User>> {
    // 1. 验证
    const user = new User(userData);
    const validation = this.validator.validate(user);
    if (!validation.isValid) {
      return { success: false, errors: validation.errors };
    }

    // 2. 密码哈希
    user.password = await this.passwordService.hash(user.password);

    // 3. 保存用户
    const createdUser = await this.repository.create(user);

    // 4. 并行执行:发送邮件 + 日志 + 分析
    await Promise.all([
      this.notificationService.sendWelcomeEmail(createdUser),
      (async () => {
        this.auditLogger.logUserAction(createdUser.id!, 'register', { email: createdUser.email });
        this.analytics.identify(createdUser.id!, { email: createdUser.email });
      })()
    ]);

    return { success: true, data: createdUser };
  }
}

// ============= 组合与注入 =============

// 应用启动
const createUserModule = () => {
  // 基础设施层
  const db = new PostgresqlDatabase();
  const emailService = new SendGridEmailService();
  const templateEngine = new HandlebarsTemplateEngine();

  // 数据层
  const userRepo = new UserRepository(db);

  // 领域服务
  const validator = new UserValidator();
  const passwordService = new BcryptPasswordService();
  const authService = new JwtAuthenticationService(userRepo, passwordService, config.jwt);
  const notifier = new EmailNotificationService(emailService, templateEngine);
  const auditLogger = new WinstonAuditLogger();
  const analytics = new SegmentAnalyticsService();

  // 用例层
  const registerUseCase = new RegisterUserUseCase(
    validator, userRepo, passwordService, notifier, auditLogger, analytics
  );

  return { registerUseCase, authService };
};

// API层
app.post('/api/register', async (req, res) => {
  const { registerUseCase } = createUserModule();
  
  const result = await registerUseCase.execute(req.body);
  
  if (!result.success) {
    return res.status(400).json({ errors: result.errors });
  }
  
  res.status(201).json(result.data);
});

严格模式下的类型安全亮点

  • createdUser.id! 断言配合构造函数验证,确保ID存在

  • Result<User> 泛型结果模式,强制处理成功/失败两种情况

  • Readonlyreadonly 防止数据在流程中被意外修改

  • 每个服务的依赖通过构造函数注入,类型系统确保完整性


五、总结:接口分离的黄金法则

1. 识别职责边界的技巧

在TypeScript严格模式下,可以通过类型复杂度来识别职责:

  • 如果一个接口有超过5个方法:可能职责过大

  • 如果实现类需要处理大量不相关的状态:需要拆分

  • 如果测试时需要mock太多无关方法:接口需要细化

2. 接口粒度建议

接口大小 适用场景 TypeScript严格模式提示
细粒度(1-2方法) 基础设施服务(邮件、日志) 易于mock,测试简洁
中粒度(3-5方法) 领域服务(订单处理) 职责聚焦,类型安全
粗粒度(5+方法) 仅用于聚合根(谨慎使用) 需要拆分成小接口

3. 依赖注入模式

在严格模式下,推荐构造函数注入 + readonly

TypeScript

复制

TypeScript 复制代码
class Service {
  constructor(
    private readonly repo: IUserRepository, // 不可变,类型安全
    private readonly logger: ILogger
  ) {}
}

4. 测试愉悦度对比

TypeScript 复制代码
// ❌ 违反SRP:需要mock整个UserManager
const mockManager = {
  createUser: jest.fn(),
  validateEmail: jest.fn(),
  hashPassword: jest.fn(),
  sendEmail: jest.fn(),
  log: jest.fn(),
  // ...还要mock10个不相关的方法
};

// ✅ 遵守SRP:只mock需要的接口
const mockValidator: IUserValidator = {
  validate: jest.fn().mockReturnValue({ isValid: true, errors: [] }),
  validateEmail: jest.fn().mockReturnValue(true),
  validatePassword: jest.fn().mockReturnValue(true)
};

5. 严格模式下的最佳实践

必须启用

TypeScript 复制代码
{
  "compilerOptions": {
    "strict": true,
    "strictNullChecks": true,
    "strictPropertyInitialization": true,
    "noImplicitAny": true,
    "noUnusedLocals": true
  }
}

代码规范

  • 所有接口属性使用 readonly

  • 所有服务类属性使用 private readonly

  • 数组参数使用 ReadonlyArray<T>

  • 返回 null 必须显式声明类型

  • 使用 unknown 而非 any 表示不确定类型


六、核心要点回顾

通过接口实现职责分离的三步法

  1. 识别职责:找到"改变的原因"(数据库变化、业务规则变化、UI变化等)

  2. 定义接口:为每个职责创建独立接口(Ixxxable命名约定)

  3. 组合实现:实现类按需签署接口,用例层组合所需服务

最终效果:

  • 可维护:修改邮件服务不影响订单计算

  • 可测试:每个接口可独立mock

  • 灵活:可替换任何实现而不影响高层

  • 类型安全:TypeScript严格模式全程保驾护航

记住:接口是职责的边界,不是实现的约束。拥抱接口分离,让你的代码像乐高积木一样自由组合!

相关推荐
踢球的打工仔2 小时前
typescript-引用和const常量
前端·javascript·typescript
小二·3 小时前
前端测试体系完全指南:从 Vitest 单元测试到 Cypress E2E(Vue 3 + TypeScript)
前端·typescript·单元测试
如果你好4 小时前
TypeScript函数类型全攻略:从基础约束到高级玩法
typescript
hboot19 小时前
别再被 TS 类型冲突折磨了!一文搞懂类型合并规则
前端·typescript
王林不想说话19 小时前
提升工作效率的Utils
前端·javascript·typescript
cute_ming20 小时前
从 Node.js + TypeScript 无缝切换到 Python 的最佳实践
python·typescript·node.js
T112421 小时前
深入解析 OpenCode:下一代 AI 编程助手的架构艺术
typescript·aigc
小二·1 天前
Vite 构建完全指南:极致性能优化、安全加固与自动化部署(Vue 3 + TypeScript)
安全·性能优化·typescript
一只爱吃糖的小羊1 天前
从 AnyScript 到 TypeScript:如何利用 Type Guards 与 Type Predicates 实现精准的类型锁死
前端·javascript·typescript
先生沉默先1 天前
TypeScript 学习_类型与语法(2)
学习·typescript