Skip to content
Method约 1 分钟0 个小节更新于 2026/06/19在线编辑

设计模式总览

设计模式不是为了让代码看起来“高级”,而是为了解决对象协作中反复出现的问题。学习设计模式时,不要只背模式名字,要先想清楚三个问题:

  • 这段代码现在的变化点在哪里?
  • 哪些对象之间耦合太紧?
  • 用模式之后,是不是让扩展更容易、维护更清楚?

学习顺序

建议按下面顺序学习:

text
面向对象原则
  -> 创建型模式:对象怎么创建
  -> 结构型模式:对象怎么组合
  -> 行为型模式:对象怎么协作
  -> 回到业务代码中判断是否真的需要模式

设计模式的本质是“把变化封装起来”。如果一个地方不会变化,强行套模式只会让代码更复杂。

七大原则

单一职责原则

一个类只负责一类明确的事情。类的职责越混乱,修改时越容易牵一发动全身。

常见坏味道:

  • 一个 UserService 同时处理用户注册、短信发送、Excel 导出、权限校验。
  • 一个工具类越写越大,里面混着日期、文件、HTTP、加密、数据库操作。

优化思路:

text
注册流程 -> UserService
短信发送 -> SmsService
权限判断 -> PermissionService
报表导出 -> UserExportService

单一职责不是要求“一个类只有一个方法”,而是要求一个类只有一个清晰的变化原因。

开闭原则

对扩展开放,对修改关闭。新增功能时尽量通过新增类、组合、配置来完成,而不是反复修改老代码。

典型场景:

java
if ("wechat".equals(type)) {
    // 微信支付
} else if ("alipay".equals(type)) {
    // 支付宝支付
} else if ("bank".equals(type)) {
    // 银行卡支付
}

当支付方式越来越多时,这段代码会越来越难维护。可以用策略模式或工厂模式把不同支付方式拆出去。

开闭原则不是绝对不修改旧代码,而是把高频变化点设计成容易扩展。

里氏替换原则

子类应该可以替换父类,并且不破坏原有程序逻辑。

如果父类方法承诺“可以支付”,子类却改成“部分情况下直接抛异常”,调用方就会被破坏。

错误示例:

java
class Bird {
    void fly() {}
}

class Penguin extends Bird {
    @Override
    void fly() {
        throw new UnsupportedOperationException();
    }
}

企鹅不是“会飞的鸟”,更合理的抽象是:

java
interface Bird {}

interface Flyable {
    void fly();
}

依赖倒置原则

高层模块不要依赖低层实现,二者都应该依赖抽象。

不要让业务代码直接绑定某个具体实现:

java
class OrderService {
    private final MysqlOrderRepository repository = new MysqlOrderRepository();
}

更好的方式是依赖接口:

java
interface OrderRepository {
    void save(Order order);
}

class OrderService {
    private final OrderRepository repository;

    OrderService(OrderRepository repository) {
        this.repository = repository;
    }
}

这样以后从 MySQL 换成 Redis、MongoDB、远程接口时,业务层不需要跟着改。

接口隔离原则

接口应该小而专一,调用方不应该被迫依赖自己用不到的方法。

坏味道:

java
interface Worker {
    void code();
    void test();
    void deploy();
    void designUI();
}

后端开发可能不需要 designUI(),运维可能不需要 code()。可以拆成多个小接口:

java
interface Developer {
    void code();
}

interface Tester {
    void test();
}

interface Deployer {
    void deploy();
}

接口隔离可以减少无意义实现,也能让职责边界更清楚。

迪米特法则

一个对象应该尽量少知道其他对象的内部细节。也叫“最少知识原则”。

坏味道:

java
order.getUser().getAddress().getCity().getCode();

调用方知道了太多对象内部结构,一旦中间结构变化,调用方也要改。可以把行为封装到对象内部:

java
order.getDeliveryCityCode();

迪米特法则不是禁止对象协作,而是避免调用方穿透多层对象去操作内部细节。

合成复用原则

优先使用组合,而不是继承。

继承适合稳定的“是一个”关系,组合适合可变的“有一个”能力。

java
class OrderService {
    private final DiscountPolicy discountPolicy;
    private final PaymentClient paymentClient;
}

组合的好处是可以替换、可以测试、可以按业务场景装配。继承层级一旦太深,扩展会变得很僵硬。

UML 关系速记

关系含义Java 中常见表现
依赖临时使用对方方法参数、局部变量
关联长期知道对方成员变量
聚合整体和部分,部分可独立存在班级和学生
组合整体和部分,生命周期强绑定订单和订单明细
继承子类继承父类extends
实现类实现接口implements

读类图时重点看“谁持有谁”“谁调用谁”“谁负责创建谁”,不要只看箭头样式。

模式分类

创建型模式

关注对象创建,解决“对象怎么来”的问题。

结构型模式

关注对象组合,解决“对象怎么组织起来”的问题。

行为型模式

关注对象协作,解决“对象之间怎么交互”的问题。

选择模式的判断方法

可以按下面的方式判断:

代码问题优先考虑
创建对象逻辑复杂、到处 new工厂、建造者
系统只需要一个共享实例单例
需要复制复杂对象原型
老接口和新接口不兼容适配器
多个维度独立变化桥接
不改原类增加功能装饰器、代理
树形结构统一处理组合
子系统太复杂,需要统一入口外观
大量重复小对象享元
多个处理器按顺序处理请求责任链
if-else 选择不同算法策略
状态变化导致行为变化状态
固定流程中部分步骤变化模板方法
一对多通知观察者

学习建议

  • 先理解场景,再看代码。
  • 先写普通实现,再重构成模式。
  • 模式不是越多越好,能让代码更清楚才有价值。
  • 在 Spring、JDK、MyBatis 里找模式,比单独背示例更有效。

以工程实践沉淀知识,以文档复盘成长。