设计模式是软件工程中最经典也最实用的概念之一。它们是经过验证的解决方案模板,能够帮助开发者解决常见的软件设计问题。然而,很多开发者对设计模式的理解还停留在"知道名字"或"能识别模式"的阶段,在实际项目中却很少主动应用。本文将从实践角度出发,探讨如何真正将设计模式融入到日常开发中。
理解设计模式的本质
不是规则,而是工具
设计模式不是必须遵守的规则,而是解决问题的工具。就像锤子、扳手、螺丝刀一样,每种模式都有其适用的场景。强行使用设计模式只会让代码变得复杂和难以理解。
核心原则:不要为了使用模式而使用模式。只在真正需要解决问题时才应用模式。
模式的分类
根据用途,设计模式可以分为三类:
创建型模式(Creational):解决对象创建的问题
- 单例模式(Singleton)
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
- 建造者模式(Builder)
- 原型模式(Prototype)
结构型模式(Structural):处理类和对象的组合
- 适配器模式(Adapter)
- 桥接模式(Bridge)
- 组合模式(Composite)
- 装饰器模式(Decorator)
- 外观模式(Facade)
- 享元模式(Flyweight)
- 代理模式(Proxy)
行为型模式(Behavioral):处理对象之间的通信
- 策略模式(Strategy)
- 模板方法模式(Template Method)
- 观察者模式(Observer)
- 迭代器模式(Iterator)
- 责任链模式(Chain of Responsibility)
- 命令模式(Command)
- 备忘录模式(Memento)
- 状态模式(State)
- 访问者模式(Visitor)
- 中介者模式(Mediator)
- 解释器模式(Interpreter)
创建型模式实战
单例模式:何时真正需要
单例模式是最被滥用的设计模式之一。很多人使用单例是因为"全局访问很方便",但这往往是一个坏习惯。
适用场景:
- 日志系统:整个应用程序只需要一个日志记录器
- 配置管理:配置信息在应用运行期间不变
- 数据库连接池:管理有限的数据库连接资源
不适用场景:
- “为了方便访问"的全局对象
- 可以用依赖注入替代的场景
- 需要多个实例的场景
最佳实践示例:
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');
注意事项:
- 考虑线程安全问题(在多线程环境中)
- 单例的生命周期管理
- 测试时的单例重置
工厂方法模式:简化对象创建
当对象的创建逻辑复杂或需要根据条件创建不同类型的对象时,工厂方法模式非常有用。
实际应用场景:
- UI 组件库:根据配置创建不同类型的按钮、输入框
- 支付系统:根据支付方式创建不同的支付处理器
- 数据解析器:根据文件类型创建不同的解析器
示例代码:
// 支付处理器接口
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' });
何时使用设计模式
识别模式适用的信号
代码坏味道:
- 大量的 if-else 或 switch 语句 → 策略模式
- 创建对象的逻辑复杂 → 工厂方法、建造者模式
- 类之间紧密耦合,难以修改 → 适配器、桥接模式
- 需要在不修改原始代码的情况下添加功能 → 装饰器模式
- 对象之间的交互复杂 → 中介者模式
需求变化:
- 同一行为有多种实现 → 策略模式
- 需要动态地添加或删除对象的功能 → 装饰器模式
- 一个状态改变会影响多个对象 → 观察者模式
- 需要访问复杂对象结构中的元素 → 访问者模式
模式的代价
设计模式不是银弹,它们也有代价:
复杂性:引入模式会增加代码的复杂性。如果问题本身很简单,引入模式可能是过度设计。
学习成本:团队成员需要理解模式的概念和用法,这增加了学习成本。
性能开销:某些模式会带来额外的性能开销,如对象创建、间接调用等。
过度抽象:如果过度使用抽象,代码可能会变得难以理解和调试。
实践建议
从小处着手:不要一开始就试图应用所有模式。选择最简单、最能解决当前问题的模式。
重构引入:很多时候,先写出"能用"的代码,然后在重构时引入模式。这样更容易理解模式的价值。
团队共识:确保团队成员对使用的模式有共同的理解和约定。
文档化:在使用模式时,添加注释说明为什么使用这个模式,以及模式的作用。
定期回顾:定期回顾代码,评估模式的使用是否合理,是否需要调整或移除。
与现代框架的结合
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 {};
}
}
总结
设计模式是软件工程中宝贵的财富,但它们不是目的,而是手段。正确使用设计模式的关键是:
- 理解问题:首先明确要解决什么问题
- 选择合适的模式:根据问题选择最合适的模式,或者选择不使用模式
- 保持简单:不要为了使用模式而增加不必要的复杂性
- 团队协作:确保团队成员对使用的模式有共同的理解
- 持续改进:定期回顾和评估模式的使用效果
记住,最好的代码不是使用最多模式的代码,而是最清晰、最易维护、最能解决问题的代码。设计模式应该是工具箱中的工具,而不是束缚创造力的枷锁。
参考资料:
- Gang of Four. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994.
- Martin Fowler. Patterns of Enterprise Application Architecture. Addison-Wesley, 2002.
- Refactoring Guru. Design Patterns. https://refactoring.guru/design-patterns
- Clean Code by Robert C. Martin