跳到主要内容

设计模式教程

设计模式(Design Patterns)是软件开发中常见问题的经典解决方案。它们不是代码,而是解决特定问题的思想和方法论。本教程将带你深入理解 23 种经典设计模式,掌握面向对象设计的核心思想。

什么是设计模式?

设计模式是一套被反复使用、多数人知晓的、经过分类编目的代码设计经验的总结。它描述了在软件设计过程中一些不断重复发生的问题,以及该问题的核心解决方案。

设计模式的起源

设计模式的概念最初来源于建筑学。建筑师 Christopher Alexander 在其著作《建筑模式语言》中提出了一种描述建筑设计中常见问题的模式语言。

1994 年,Erich Gamma、John Vlissides、Ralph Johnson 和 Richard Helm(被称为"四人帮"或 GoF)出版了《设计模式:可复用面向对象软件的基础》一书,将模式概念引入软件开发领域,总结了 23 种经典设计模式。

为什么学习设计模式?

  1. 提高代码质量:设计模式是经过验证的最佳实践,使用它们可以避免常见的设计错误
  2. 增强沟通效率:设计模式提供了一套通用的词汇,开发人员可以用"工厂模式"、"观察者模式"等术语快速交流设计思想
  3. 提升设计能力:学习设计模式能帮助你理解面向对象设计的核心原则
  4. 面试必备:设计模式是技术面试的高频考点

设计模式分类

设计模式根据其目的分为三大类:

创建型模式(Creational Patterns)

创建型模式关注对象的创建过程,将对象的创建和使用分离。

模式简介使用场景
单例模式确保一个类只有一个实例配置管理器、日志记录器
工厂方法模式定义创建对象的接口,让子类决定实例化哪个类日志记录器、数据库连接
抽象工厂模式创建相关或依赖对象的家族跨平台 UI 组件
建造者模式将复杂对象的构建与表示分离构建复杂配置对象
原型模式通过复制现有对象来创建新对象创建成本高的对象

结构型模式(Structural Patterns)

结构型模式关注类和对象的组合,通过继承或组合来构建更大的结构。

模式简介使用场景
适配器模式将一个类的接口转换成客户期望的另一个接口接口不兼容的系统集成
桥接模式将抽象与实现分离,使它们可以独立变化JDBC 驱动
组合模式将对象组合成树形结构以表示"部分-整体"层次文件系统、组织架构
装饰器模式动态地给对象添加额外职责Java I/O 流
外观模式为子系统中的一组接口提供一个统一入口简化复杂系统调用
享元模式共享细粒度对象以减少内存占用字符串常量池
代理模式为其他对象提供代理以控制访问远程代理、虚拟代理

行为型模式(Behavioral Patterns)

行为型模式关注对象之间的通信和职责分配。

模式简介使用场景
策略模式定义一系列算法,使它们可以互相替换支付方式选择
观察者模式定义对象间一对多的依赖关系事件监听、消息订阅
命令模式将请求封装成对象菜单操作、事务处理
迭代器模式提供一种方法顺序访问聚合对象中的元素集合遍历
模板方法模式定义算法骨架,将某些步骤延迟到子类Servlet 生命周期
状态模式允许对象在内部状态改变时改变行为订单状态流转
责任链模式将请求沿链传递,直到有对象处理过滤器链
中介者模式用中介对象封装对象间的交互MVC 框架
备忘录模式在不破坏封装的前提下捕获对象内部状态撤销操作
访问者模式在不改变数据结构的前提下定义新操作编译器语法树

设计原则

设计模式的核心是遵循以下设计原则(SOLID 原则):

1. 单一职责原则(Single Responsibility Principle, SRP)

一个类应该只有一个引起它变化的原因。即一个类只负责一项职责。

反例

// 一个类负责太多事情
public class User {
public void login() { /* 登录逻辑 */ }
public void sendEmail() { /* 发送邮件 */ }
public void generateReport() { /* 生成报告 */ }
}

正例

// 职责分离
public class User {
private String username;
// 用户相关属性和方法
}

public class AuthService {
public void login(User user) { /* 登录逻辑 */ }
}

public class EmailService {
public void sendEmail(User user) { /* 发送邮件 */ }
}

2. 开闭原则(Open-Closed Principle, OCP)

软件实体应该对扩展开放,对修改关闭。即在不修改现有代码的情况下扩展功能。

反例

// 每次新增形状都需要修改这个类
public class AreaCalculator {
public double calculate(Object shape) {
if (shape instanceof Circle) {
return Math.PI * ((Circle) shape).getRadius() * ((Circle) shape).getRadius();
} else if (shape instanceof Rectangle) {
return ((Rectangle) shape).getWidth() * ((Rectangle) shape).getHeight();
}
return 0;
}
}

正例

// 使用多态,新增形状只需实现接口
public interface Shape {
double calculateArea();
}

public class Circle implements Shape {
private double radius;
public double calculateArea() {
return Math.PI * radius * radius;
}
}

public class Rectangle implements Shape {
private double width, height;
public double calculateArea() {
return width * height;
}
}

3. 里氏替换原则(Liskov Substitution Principle, LSP)

子类对象必须能够替换其父类对象,且程序行为正确。

反例

// 正方形继承矩形违反了 LSP
public class Rectangle {
protected int width, height;
public void setWidth(int w) { width = w; }
public void setHeight(int h) { height = h; }
public int getArea() { return width * height; }
}

public class Square extends Rectangle {
@Override
public void setWidth(int w) {
width = w;
height = w; // 强制宽高相等
}
@Override
public void setHeight(int h) {
width = h;
height = h;
}
}

// 问题:Rectangle rect = new Square();
// rect.setWidth(5); rect.setHeight(4);
// 面积应该是 20,但实际是 16

4. 接口隔离原则(Interface Segregation Principle, ISP)

客户端不应该依赖它不需要的接口。应该将大接口拆分为多个小接口。

反例

// 一个臃肿的接口
public interface Worker {
void work();
void eat();
void sleep();
}

// 机器人不需要吃饭睡觉
public class Robot implements Worker {
public void work() { /* 工作 */ }
public void eat() { /* 空实现,不需要 */ }
public void sleep() { /* 空实现,不需要 */ }
}

正例

// 接口拆分
public interface Workable {
void work();
}

public interface Feedable {
void eat();
}

public interface Sleepable {
void sleep();
}

public class Human implements Workable, Feedable, Sleepable {
public void work() { /* 工作 */ }
public void eat() { /* 吃饭 */ }
public void sleep() { /* 睡觉 */ }
}

public class Robot implements Workable {
public void work() { /* 工作 */ }
}

5. 依赖倒置原则(Dependency Inversion Principle, DIP)

高层模块不应该依赖低层模块,两者都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象。

反例

// 高层模块直接依赖低层模块
public class UserService {
private MySQLDatabase database = new MySQLDatabase();

public void saveUser(User user) {
database.save(user);
}
}

正例

// 高层模块依赖抽象
public interface Database {
void save(User user);
}

public class MySQLDatabase implements Database {
public void save(User user) { /* MySQL 保存逻辑 */ }
}

public class PostgreSQLDatabase implements Database {
public void save(User user) { /* PostgreSQL 保存逻辑 */ }
}

public class UserService {
private Database database;

public UserService(Database database) {
this.database = database;
}

public void saveUser(User user) {
database.save(user);
}
}

其他重要原则

迪米特法则(Law of Demeter, LoD)

一个对象应该对其他对象有尽可能少的了解。又称"最少知识原则"。

核心思想:只与你的直接朋友通信,不跟"陌生人"说话。

// 反例:链式调用暴露了太多内部结构
user.getOrder().getItem().getProduct().getPrice();

// 正例:让对象自己处理内部逻辑
user.getOrderTotalPrice();

组合复用原则(Composite Reuse Principle, CRP)

尽量使用组合而非继承来达到复用目的。

// 继承方式(耦合度高)
public class Dog extends Animal {
public void bark() { /* 汪汪叫 */ }
}

// 组合方式(更灵活)
public class Dog {
private Animal animal;
private BarkBehavior barkBehavior;

public void bark() {
barkBehavior.bark();
}
}

本教程的约定

示例语言

本教程使用 Java 作为示例语言,因为:

  1. Java 是面向对象编程的经典语言
  2. 大多数设计模式书籍使用 Java 示例
  3. Java 语法清晰,易于理解

结构说明

每个设计模式的章节包含以下内容:

  1. 模式定义:模式的正式定义和核心思想
  2. 问题场景:说明什么情况下需要这个模式
  3. 解决方案:详细的实现步骤和代码示例
  4. 模式结构:UML 类图展示类之间的关系
  5. 代码实现:完整的 Java 代码示例
  6. 应用场景:实际开发中的应用案例
  7. 优缺点分析:客观分析模式的利弊
  8. 与其他模式的关系:模式的组合使用

学习路径建议

  1. 先理解原则:深入理解 SOLID 原则,这是设计模式的理论基础
  2. 从简单开始:先学习单例、工厂、策略等常用且易理解的模式
  3. 对比学习:比较相似模式的区别,如工厂方法 vs 抽象工厂
  4. 实践应用:在实际项目中识别和应用设计模式
  5. 避免过度设计:设计模式是工具,不是目的,不要为了用模式而用模式

开始学习

准备好开始学习了吗?让我们从最经典的 单例模式 开始!

参考资源

  • 《设计模式:可复用面向对象软件的基础》- GoF
  • 《Head First 设计模式》- Eric Freeman 等
  • 《Effective Java》- Joshua Bloch
  • 《重构:改善既有代码的设计》- Martin Fowler