设计模式实战:从理论到实践的完整指南

2026-02-22 09:00:00 · 9 minute read

设计模式是软件工程中最经典也最实用的概念之一。它们是经过验证的解决方案模板,能够帮助开发者解决常见的软件设计问题。然而,很多开发者对设计模式的理解还停留在"知道名字"或"能识别模式"的阶段,在实际项目中却很少主动应用。本文将从实践角度出发,探讨如何真正将设计模式融入到日常开发中。

理解设计模式的本质

不是规则,而是工具

设计模式不是必须遵守的规则,而是解决问题的工具。就像锤子、扳手、螺丝刀一样,每种模式都有其适用的场景。强行使​​用设计模式只会让代码变得复杂和难以理解。

核心原则:不要为了使用模式而使用模式。只在真正需要解决问题时才应用模式。

模式的分类

根据用途,设计模式可以分为三类:

创建型模式(Creational):解决对象创建的问题

结构型模式(Structural):处理类和对象的组合

行为型模式(Behavioral):处理对象之间的通信

创建型模式实战

单例模式:何时真正需要

单例模式是最被滥用的设计模式之一。很多人使用单例是因为"全局访问很方便",但这往往是一个坏习惯。

适用场景

不适用场景

最佳实践示例

class Logger {
  private static instance: Logger;
  private constructor(private logFile: string) {}

  static getInstance(logFile: string = 'app.log'): Logger {
    if (!Logger.instance) {
      Logger.instance = new Logger(logFile);
    }
    return Logger.instance;
  }

  log(message: string, level: 'INFO' | 'WARN' | 'ERROR'): void {
    const timestamp = new Date().toISOString();
    const logMessage = `[${timestamp}] [${level}] ${message}`;
    console.log(logMessage);

    // 写入文件的逻辑...
  }
}

// 使用
const logger = Logger.getInstance();
logger.log('Application started', 'INFO');

注意事项

工厂方法模式:简化对象创建

当对象的创建逻辑复杂或需要根据条件创建不同类型的对象时,工厂方法模式非常有用。

实际应用场景

示例代码

// 支付处理器接口
interface PaymentProcessor {
  process(amount: number): Promise<boolean>;
}

// 信用卡支付处理器
class CreditCardProcessor implements PaymentProcessor {
  async process(amount: number): Promise<boolean> {
    console.log(`Processing credit card payment: ${amount}`);
    // 实际的信用卡支付逻辑
    return true;
  }
}

// 支付宝支付处理器
class AlipayProcessor implements PaymentProcessor {
  async process(amount: number): Promise<boolean> {
    console.log(`Processing Alipay payment: ${amount}`);
    // 实际的支付宝支付逻辑
    return true;
  }
}

// 微信支付处理器
class WeChatPayProcessor implements PaymentProcessor {
  async process(amount: number): Promise<boolean> {
    console.log(`Processing WeChat Pay payment: ${amount}`);
    // 实际的微信支付逻辑
    return true;
  }
}

// 支付处理器工厂
enum PaymentType {
  CREDIT_CARD = 'credit_card',
  ALIPAY = 'alipay',
  WECHAT = 'wechat'
}

class PaymentProcessorFactory {
  static createProcessor(type: PaymentType): PaymentProcessor {
    switch (type) {
      case PaymentType.CREDIT_CARD:
        return new CreditCardProcessor();
      case PaymentType.ALIPAY:
        return new AlipayProcessor();
      case PaymentType.WECHAT:
        return new WeChatPayProcessor();
      default:
        throw new Error(`Unsupported payment type: ${type}`);
    }
  }
}

// 使用
async function makePayment(type: PaymentType, amount: number) {
  const processor = PaymentProcessorFactory.createProcessor(type);
  const success = await processor.process(amount);
  return success;
}

// 调用
makePayment(PaymentType.ALIPAY, 100);

建造者模式:复杂对象的构建

当一个对象有很多属性,其中一些是可选的,建造者模式可以提供清晰的创建方式。

示例代码

// 用户对象
class User {
  private constructor(
    public readonly id: string,
    public readonly name: string,
    public readonly email: string,
    public readonly age?: number,
    public readonly phone?: string,
    public readonly address?: string
  ) {}

  toString(): string {
    return `User(${this.id}, ${this.name}, ${this.email})`;
  }
}

// 用户建造者
class UserBuilder {
  private id: string = '';
  private name: string = '';
  private email: string = '';
  private age?: number;
  private phone?: string;
  private address?: string;

  setId(id: string): this {
    this.id = id;
    return this;
  }

  setName(name: string): this {
    this.name = name;
    return this;
  }

  setEmail(email: string): this {
    this.email = email;
    return this;
  }

  setAge(age: number): this {
    this.age = age;
    return this;
  }

  setPhone(phone: string): this {
    this.phone = phone;
    return this;
  }

  setAddress(address: string): this {
    this.address = address;
    return this;
  }

  build(): User {
    if (!this.id || !this.name || !this.email) {
      throw new Error('Missing required fields');
    }
    return new User(
      this.id,
      this.name,
      this.email,
      this.age,
      this.phone,
      this.address
    );
  }
}

// 使用
const user = new UserBuilder()
  .setId('123')
  .setName('John Doe')
  .setEmail('john@example.com')
  .setAge(30)
  .setPhone('123-456-7890')
  .setAddress('123 Main St')
  .build();

console.log(user.toString());

结构型模式实战

适配器模式:让不兼容的接口协同工作

适配器模式在实际项目中非常常见,特别是集成第三方库或遗留系统时。

示例场景:集成不同的支付网关 API。

// 第三方支付网关 A 的接口
interface PaymentGatewayA {
  charge(amount: number, currency: string): Promise<{ success: boolean; transactionId: string }>;
}

// 第三方支付网关 B 的接口
interface PaymentGatewayB {
  makePayment(value: number, currencyCode: string): Promise<{ ok: boolean; reference: string }>;
}

// 我们的统一支付接口
interface UnifiedPaymentGateway {
  pay(amount: number, currency: string): Promise<{ success: boolean; transactionId: string }>;
}

// 支付网关 A 的适配器
class PaymentGatewayAAdapter implements UnifiedPaymentGateway {
  constructor(private gatewayA: PaymentGatewayA) {}

  async pay(amount: number, currency: string) {
    const result = await this.gatewayA.charge(amount, currency);
    return {
      success: result.success,
      transactionId: result.transactionId
    };
  }
}

// 支付网关 B 的适配器
class PaymentGatewayBAdapter implements UnifiedPaymentGateway {
  constructor(private gatewayB: PaymentGatewayB) {}

  async pay(amount: number, currency: string) {
    const result = await this.gatewayB.makePayment(amount, currency);
    return {
      success: result.ok,
      transactionId: result.reference
    };
  }
}

// 使用
class PaymentService {
  private gateway: UnifiedPaymentGateway;

  constructor(gateway: UnifiedPaymentGateway) {
    this.gateway = gateway;
  }

  async processPayment(amount: number, currency: string) {
    return await this.gateway.pay(amount, currency);
  }
}

// 可以轻松切换不同的支付网关
const gatewayA = {} as PaymentGatewayA;
const gatewayB = {} as PaymentGatewayB;

const paymentServiceA = new PaymentService(new PaymentGatewayAAdapter(gatewayA));
const paymentServiceB = new PaymentService(new PaymentGatewayBAdapter(gatewayB));

装饰器模式:动态添加功能

装饰器模式允许在不修改原始对象的情况下,动态地给对象添加新的行为。这在日志记录、缓存、权限验证等场景中非常有用。

示例代码

// 定义数据访问接口
interface UserRepository {
  getUser(id: string): Promise<User>;
  saveUser(user: User): Promise<void>;
}

// 基础实现
class DatabaseUserRepository implements UserRepository {
  async getUser(id: string): Promise<User> {
    console.log('Querying database for user:', id);
    // 实际的数据库查询
    return {} as User;
  }

  async saveUser(user: User): Promise<void> {
    console.log('Saving user to database:', user.id);
    // 实际的数据库保存
  }
}

// 缓存装饰器
class CachedUserRepository implements UserRepository {
  private cache = new Map<string, User>();
  private cacheTimeout = 5 * 60 * 1000; // 5 分钟

  constructor(private repository: UserRepository) {}

  async getUser(id: string): Promise<User> {
    // 检查缓存
    const cached = this.cache.get(id);
    if (cached) {
      console.log('User found in cache:', id);
      return cached;
    }

    // 从原始仓库获取
    const user = await this.repository.getUser(id);

    // 存入缓存
    this.cache.set(id, user);

    // 设置过期
    setTimeout(() => {
      this.cache.delete(id);
    }, this.cacheTimeout);

    return user;
  }

  async saveUser(user: User): Promise<void> {
    // 保存到数据库
    await this.repository.saveUser(user);

    // 清除缓存
    this.cache.delete(user.id);
  }
}

// 日志装饰器
class LoggingUserRepository implements UserRepository {
  constructor(private repository: UserRepository) {}

  async getUser(id: string): Promise<User> {
    console.log(`[LOG] Getting user: ${id}`);
    const start = Date.now();

    try {
      const user = await this.repository.getUser(id);
      const duration = Date.now() - start;
      console.log(`[LOG] Successfully retrieved user ${id} in ${duration}ms`);
      return user;
    } catch (error) {
      console.error(`[LOG] Failed to retrieve user ${id}:`, error);
      throw error;
    }
  }

  async saveUser(user: User): Promise<void> {
    console.log(`[LOG] Saving user: ${user.id}`);
    const start = Date.now();

    try {
      await this.repository.saveUser(user);
      const duration = Date.now() - start;
      console.log(`[LOG] Successfully saved user ${user.id} in ${duration}ms`);
    } catch (error) {
      console.error(`[LOG] Failed to save user ${user.id}:`, error);
      throw error;
    }
  }
}

// 组合使用装饰器
const baseRepository = new DatabaseUserRepository();
const cachedRepository = new CachedUserRepository(baseRepository);
const loggingCachedRepository = new LoggingUserRepository(cachedRepository);

// 使用
await loggingCachedRepository.getUser('123');

行为型模式实战

策略模式:算法族的封装

策略模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。这让算法独立于使用它的客户端而变化。

示例场景:订单折扣计算。

// 折扣策略接口
interface DiscountStrategy {
  calculateDiscount(order: Order): number;
}

// 订单类型
type OrderType = 'regular' | 'vip' | 'seasonal' | 'bulk';

// 订单
class Order {
  constructor(
    public readonly id: string,
    public readonly total: number,
    public readonly type: OrderType,
    public readonly itemCount: number
  ) {}
}

// 无折扣策略
class NoDiscountStrategy implements DiscountStrategy {
  calculateDiscount(order: Order): number {
    return 0;
  }
}

// VIP 客户折扣策略
class VipDiscountStrategy implements DiscountStrategy {
  calculateDiscount(order: Order): number {
    return order.total * 0.1; // 10% 折扣
  }
}

// 季节性折扣策略
class SeasonalDiscountStrategy implements DiscountStrategy {
  calculateDiscount(order: Order): number {
    return order.total * 0.15; // 15% 折扣
  }
}

// 批量购买折扣策略
class BulkDiscountStrategy implements DiscountStrategy {
  calculateDiscount(order: Order): number {
    if (order.itemCount >= 10) {
      return order.total * 0.2; // 20% 折扣
    } else if (order.itemCount >= 5) {
      return order.total * 0.1; // 10% 折扣
    }
    return 0;
  }
}

// 折扣计算器
class DiscountCalculator {
  private strategies: Map<OrderType, DiscountStrategy>;

  constructor() {
    this.strategies = new Map([
      ['regular', new NoDiscountStrategy()],
      ['vip', new VipDiscountStrategy()],
      ['seasonal', new SeasonalDiscountStrategy()],
      ['bulk', new BulkDiscountStrategy()]
    ]);
  }

  setStrategy(type: OrderType, strategy: DiscountStrategy): void {
    this.strategies.set(type, strategy);
  }

  calculateDiscount(order: Order): number {
    const strategy = this.strategies.get(order.type);
    if (!strategy) {
      throw new Error(`No discount strategy for order type: ${order.type}`);
    }
    return strategy.calculateDiscount(order);
  }
}

// 使用
const calculator = new DiscountCalculator();

const vipOrder = new Order('1', 1000, 'vip', 1);
const vipDiscount = calculator.calculateDiscount(vipOrder);
console.log('VIP discount:', vipDiscount); // 100

const bulkOrder = new Order('2', 2000, 'bulk', 15);
const bulkDiscount = calculator.calculateDiscount(bulkOrder);
console.log('Bulk discount:', bulkDiscount); // 400

// 可以动态修改策略
calculator.setStrategy('vip', new SeasonalDiscountStrategy());
const newVipDiscount = calculator.calculateDiscount(vipOrder);
console.log('New VIP discount:', newVipDiscount); // 150

观察者模式:事件驱动的实现

观察者模式定义对象间的一对多依赖,当一个对象状态改变时,所有依赖它的对象都会得到通知并自动更新。这在现代前端框架中非常常见。

示例代码

// 观察者接口
interface Observer {
  update(data: any): void;
}

// 主题接口
interface Subject {
  attach(observer: Observer): void;
  detach(observer: Observer): void;
  notify(data: any): void;
}

// 具体主题:用户状态管理器
class UserManager implements Subject {
  private observers: Observer[] = [];
  private user: User | null = null;

  attach(observer: Observer): void {
    if (!this.observers.includes(observer)) {
      this.observers.push(observer);
    }
  }

  detach(observer: Observer): void {
    const index = this.observers.indexOf(observer);
    if (index > -1) {
      this.observers.splice(index, 1);
    }
  }

  notify(data: any): void {
    for (const observer of this.observers) {
      observer.update(data);
    }
  }

  setUser(user: User | null): void {
    this.user = user;
    this.notify({ type: 'USER_CHANGED', user });
  }

  getUser(): User | null {
    return this.user;
  }
}

// 具体观察者:UI 更新器
class UIUpdater implements Observer {
  update(data: any): void {
    console.log('[UI] Updating UI with data:', data);
    // 实际的 UI 更新逻辑
  }
}

// 具体观察者:日志记录器
class ActivityLogger implements Observer {
  update(data: any): void {
    console.log('[Logger] Logging activity:', data.type);
    // 实际的日志记录逻辑
  }
}

// 具体观察者:分析追踪器
class AnalyticsTracker implements Observer {
  update(data: any): void {
    console.log('[Analytics] Tracking:', data.type);
    // 实际的分析追踪逻辑
  }
}

// 使用
const userManager = new UserManager();

// 添加观察者
const uiUpdater = new UIUpdater();
const activityLogger = new ActivityLogger();
const analyticsTracker = new AnalyticsTracker();

userManager.attach(uiUpdater);
userManager.attach(activityLogger);
userManager.attach(analyticsTracker);

// 用户登录
userManager.setUser({ id: '123', name: 'John' });

// 用户登出
userManager.setUser(null);

// 移除某个观察者
userManager.detach(activityLogger);

// 再次用户登录(activityLogger 不会被通知)
userManager.setUser({ id: '456', name: 'Jane' });

何时使用设计模式

识别模式适用的信号

代码坏味道

需求变化

模式的代价

设计模式不是银弹,它们也有代价:

复杂性:引入模式会增加代码的复杂性。如果问题本身很简单,引入模式可能是过度设计。

学习成本:团队成员需要理解模式的概念和用法,这增加了学习成本。

性能开销:某些模式会带来额外的性能开销,如对象创建、间接调用等。

过度抽象:如果过度使用抽象,代码可能会变得难以理解和调试。

实践建议

从小处着手:不要一开始就试图应用所有模式。选择最简单、最能解决当前问题的模式。

重构引入:很多时候,先写出"能用"的代码,然后在重构时引入模式。这样更容易理解模式的价值。

团队共识:确保团队成员对使用的模式有共同的理解和约定。

文档化:在使用模式时,添加注释说明为什么使用这个模式,以及模式的作用。

定期回顾:定期回顾代码,评估模式的使用是否合理,是否需要调整或移除。

与现代框架的结合

React 中的设计模式

容器/展示组件模式

// 展示组件(只关注 UI)
interface UserListProps {
  users: User[];
  onSelectUser: (user: User) => void;
}

function UserList({ users, onSelectUser }: UserListProps) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id} onClick={() => onSelectUser(user)}>
          {user.name}
        </li>
      ))}
    </ul>
  );
}

// 容器组件(处理数据和逻辑)
function UserListContainer() {
  const [users, setUsers] = useState<User[]>([]);

  useEffect(() => {
    fetchUsers().then(setUsers);
  }, []);

  const handleSelectUser = (user: User) => {
    console.log('Selected user:', user);
  };

  return <UserList users={users} onSelectUser={handleSelectUser} />;
}

自定义 Hooks 模式(类似策略模式):

// 使用不同的 Hooks 策略
function useFetch<T>(url: string): UseFetchResult<T> {
  // 实现细节...
}

function useLocalStorage<T>(key: string, initialValue: T): UseLocalStorageResult<T> {
  // 实现细节...
}

// 根据需要选择不同的数据获取策略
function useData<T>(source: 'api' | 'storage', ...args: any[]): any {
  switch (source) {
    case 'api':
      return useFetch<T>(args[0]);
    case 'storage':
      return useLocalStorage<T>(args[0], args[1]);
  }
}

Node.js 中的设计模式

中间件模式(责任链模式的变体):

type Middleware = (req: any, res: any, next: () => void) => void;

class ExpressApp {
  private middlewares: Middleware[] = [];

  use(middleware: Middleware): this {
    this.middlewares.push(middleware);
    return this;
  }

  handleRequest(req: any, res: any): void {
    let index = 0;

    const next = () => {
      if (index < this.middlewares.length) {
        const middleware = this.middlewares[index++];
        middleware(req, res, next);
      }
    };

    next();
  }
}

// 使用
const app = new ExpressApp();

app.use((req, res, next) => {
  console.log('Logging request...');
  next();
});

app.use((req, res, next) => {
  console.log('Authenticating...');
  next();
});

app.use((req, res) => {
  console.log('Handling request...');
});

单例模式(数据库连接池):

class DatabaseConnectionPool {
  private static instance: DatabaseConnectionPool;
  private connections: any[] = [];
  private maxConnections = 10;

  private constructor() {}

  static getInstance(): DatabaseConnectionPool {
    if (!DatabaseConnectionPool.instance) {
      DatabaseConnectionPool.instance = new DatabaseConnectionPool();
    }
    return DatabaseConnectionPool.instance;
  }

  getConnection(): any {
    if (this.connections.length > 0) {
      return this.connections.pop();
    }
    if (this.connections.length < this.maxConnections) {
      return this.createConnection();
    }
    throw new Error('Connection pool exhausted');
  }

  releaseConnection(connection: any): void {
    this.connections.push(connection);
  }

  private createConnection(): any {
    // 实际的连接创建逻辑
    return {};
  }
}

总结

设计模式是软件工程中宝贵的财富,但它们不是目的,而是手段。正确使用设计模式的关键是:

  1. 理解问题:首先明确要解决什么问题
  2. 选择合适的模式:根据问题选择最合适的模式,或者选择不使用模式
  3. 保持简单:不要为了使用模式而增加不必要的复杂性
  4. 团队协作:确保团队成员对使用的模式有共同的理解
  5. 持续改进:定期回顾和评估模式的使用效果

记住,最好的代码不是使用最多模式的代码,而是最清晰、最易维护、最能解决问题的代码。设计模式应该是工具箱中的工具,而不是束缚创造力的枷锁。


参考资料:

已复制