Skip to content

E15. 服务设计原则

5.1. 数据库等相对独立的模块称为一个服务

单体架构像一团乱麻,服务化设计让系统像精密齿轮般协作!

5.1.1. 单体架构的混乱示例

typescript
// 错误示例:业务逻辑与数据库操作混杂
class UserService {
    private users: { [id: number]: User } = {};

    // 用户注册同时操作数据库
    registerUser(name: string, email: string) {
        const newUser = { id: Date.now(), name, email };
        this.users[newUser.id] = newUser; // 内存模拟数据库
        this.sendWelcomeEmail(email); // 调用邮件服务
        return newUser;
    }

    private sendWelcomeEmail(email: string) {
        // 发送邮件的复杂逻辑
    }
}

5.1.2. 服务化拆分方案

typescript
// 改进方案:拆分为独立服务
interface IUserService {
    registerUser(name: string, email: string): User;
}

class UserService implements IUserService {
    constructor(private db: DatabaseService, private mailer: MailService) {}

    registerUser(name: string, email: string) {
        const user = { id: Date.now(), name, email };
        this.db.saveUser(user); // 仅负责业务逻辑
        this.mailer.sendWelcome(user.email);
        return user;
    }
}

class DatabaseService {
    private users: { [id: number]: User } = {};
    saveUser(user: User) { this.users[user.id] = user; }
}

class MailService {
    sendWelcome(email: string) { /* 发送逻辑 */ }
}

TIP

服务化优势

  • 数据库操作与业务逻辑分离
  • 邮件服务可独立替换(如换用第三方API)
  • 单元测试更简单(可Mock依赖)

5.2. 模块化的服务有利于解耦合

5.2.1. 高耦合代码的灾难

typescript
// 错误示例:类之间直接引用
class OrderService {
    private paymentGateway = new PaymentGateway(); // 直接创建依赖

    placeOrder(order: Order) {
        this.paymentGateway.processPayment(order.total);
        // 直接调用数据库
        Database.users[order.userId].balance -= order.total;
    }
}

class PaymentGateway {
    processPayment(amount: number) { /* 真实支付逻辑 */ }
}

5.2.2. 依赖注入解耦方案

typescript
// 改进方案:通过接口定义依赖
interface IPaymentService {
    processPayment(amount: number): void;
}

class OrderService {
    constructor(
        private paymentService: IPaymentService,
        private db: DatabaseService // 通过参数注入
    ) {}

    placeOrder(order: Order) {
        this.paymentService.processPayment(order.total);
        this.db.updateUserBalance(order.userId, order.total);
    }
}

// 可替换的实现
class StripePayment implements IPaymentService {
    processPayment(amount: number) { /* Stripe API */ }
}

class MockPayment implements IPaymentService {
    processPayment(amount: number) { /* 模拟支付 */ }
}
耦合方式修改支付方式所需代码量测试难度
直接引用需修改所有调用处
依赖注入仅需修改构造函数参数

5.3. 单一职责原则降低代码修改的成本

5.3.1. 职责越界的"瑞士军刀类"

typescript
// 错误示例:类承担多个职责
class UserManager {
    // 1. 用户注册
    register(email: string, password: string) { /* ... */ }

    // 2. 权限验证
    checkPermission(userId: number, action: string) { /* ... */ }

    // 3. 数据库操作
    private users: { [id: number]: User } = {};
    saveUser(user: User) { this.users[user.id] = user; }
}

5.3.2. 职责分离设计

typescript
// 改进方案:每个类专注单一职责
class UserRegistrationService {
    constructor(private db: DatabaseService) {}

    register(email: string, password: string) {
        const user = { id: Date.now(), email, password };
        this.db.saveUser(user);
        return user;
    }
}

class PermissionService {
    hasAccess(userId: number, action: string) {
        // 检查权限逻辑
        return this.db.getUserRole(userId) === "admin";
    }
}

class DatabaseService {
    private users: { [id: number]: User } = {};
    saveUser(user: User) { this.users[user.id] = user; }
    getUserRole(userId: number) { /* 查询角色 */ }
}

WARNING

职责爆炸后果

  • 单个类变更需测试所有功能
  • 新功能需求被迫挤入现有类
  • 代码修改引发连锁错误

5.4. 依赖倒转原则和开闭原则实现优雅封装

5.4.1. 传统继承的脆弱性

typescript
// 错误示例:直接依赖具体实现
abstract class NotificationService {
    send(message: string) { /* 基础实现 */ }
}

class EmailNotification extends NotificationService {
    send(message: string) { /* 发送邮件 */ }
}

class SMSNotification extends NotificationService {
    send(message: string) { /* 发送短信 */ }
}

// 客户端直接绑定具体类型
class AlertSystem {
    private notifier = new EmailNotification();
    sendAlert(message: string) { this.notifier.send(message); }
}

5.4.2. 依赖抽象的优雅设计

typescript
// 改进方案:依赖抽象接口
interface INotificationService {
    send(message: string): void;
}

class EmailService implements INotificationService {
    send(message: string) { /* 邮件实现 */ }
}

class SMSService implements INotificationService {
    send(message: string) { /* 短信实现 */ }
}

class AlertSystem {
    constructor(private notifier: INotificationService) {}

    sendAlert(message: string) {
        this.notifier.send(message);
    }
}

// 客户端可通过配置动态切换
const alert = new AlertSystem(new SMSService());
原则应用新增通知方式所需工作量扩展性
传统继承需修改 AlertSystem 类
依赖倒转仅新增实现类+配置

5.5. 服务设计中的设计模式实践

5.5.1. 工厂模式创建服务实例

typescript
// 工厂模式示例
class NotificationFactory {
    static create(type: "email" | "sms"): INotificationService {
        switch (type) {
            case "email": return new EmailService();
            case "sms": return new SMSService();
            default: throw new Error("无效类型");
        }
    }
}

// 使用工厂解耦创建逻辑
class AlertSystem {
    constructor(private type: "email" | "sms") {}

    sendAlert(message: string) {
        const service = NotificationFactory.create(this.type);
        service.send(message);
    }
}

5.5.2. 策略模式实现动态算法

typescript
// 策略模式示例
interface PaymentStrategy {
    processPayment(amount: number): void;
}

class CreditCardStrategy implements PaymentStrategy {
    processPayment(amount: number) { /* 信用卡支付 */ }
}

class PayPalStrategy implements PaymentStrategy {
    processPayment(amount: number) { /* PayPal支付 */ }
}

class CheckoutService {
    constructor(private strategy: PaymentStrategy) {}

    completePurchase() {
        this.strategy.processPayment(this.total);
    }
}

知识回顾

  1. 服务化设计:将数据库、邮件等独立功能封装为服务模块。
  2. 依赖倒转:依赖抽象接口而非具体实现,实现"对接口编程"。
  3. 单一职责:每个类只负责一个功能,降低修改成本。
  4. 开闭原则:通过扩展新增功能,避免修改现有代码。
  5. 设计模式:工厂模式解耦创建,策略模式实现动态算法选择。

课后练习

  1. (单选)以下哪项违反了单一职责原则?

    • A. UserService同时处理注册和密码重置
    • B. PaymentService仅处理支付逻辑
    • C. DatabaseService仅保存用户数据
    • D. EmailService仅发送邮件
  2. (操作)将以下代码重构为符合依赖倒转原则:

typescript
class Logger {
    log(message: string) { console.log(message); }
}

class App {
    private logger = new Logger();
    run() { this.logger.log("应用启动"); }
}
  1. (分析)为什么直接在UserService中操作数据库会增加维护成本?
参考答案
  1. A
  2. typescript
    interface ILogger { log(message: string): void }
    
    class ConsoleLogger implements ILogger { 
        log(message: string) { console.log(message); }
    }
    
    class App {
        constructor(private logger: ILogger) {}
        run() { this.logger.log("应用启动"); }
    }
  3. 数据库逻辑与业务逻辑耦合,修改数据库方案需修改所有调用处,且难以测试和替换。

扩展阅读

Built by Vitepress | Apache 2.0 Licensed