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);
}
}
知识回顾
- 服务化设计:将数据库、邮件等独立功能封装为服务模块。
- 依赖倒转:依赖抽象接口而非具体实现,实现"对接口编程"。
- 单一职责:每个类只负责一个功能,降低修改成本。
- 开闭原则:通过扩展新增功能,避免修改现有代码。
- 设计模式:工厂模式解耦创建,策略模式实现动态算法选择。
课后练习
(单选)以下哪项违反了单一职责原则?
- A.
UserService
同时处理注册和密码重置 - B.
PaymentService
仅处理支付逻辑 - C.
DatabaseService
仅保存用户数据 - D.
EmailService
仅发送邮件
- A.
(操作)将以下代码重构为符合依赖倒转原则:
typescript
class Logger {
log(message: string) { console.log(message); }
}
class App {
private logger = new Logger();
run() { this.logger.log("应用启动"); }
}
- (分析)为什么直接在
UserService
中操作数据库会增加维护成本?
参考答案
- A
- 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("应用启动"); } }
- 数据库逻辑与业务逻辑耦合,修改数据库方案需修改所有调用处,且难以测试和替换。