Skip to content

设计模式

设计模式是常见软件设计问题的经验总结。学习设计模式不是为了套模板,而是为了在合适场景下写出更稳定、可扩展、可维护的代码。

为什么要学设计模式

如果所有代码都按“想到哪写到哪”的方式增长,项目会逐渐出现这些问题:

问题后果
重复 if/else新增规则要到处改,容易漏
对具体类强依赖替换实现困难
横切逻辑散落日志、权限、事务代码重复
主流程太臃肿注册、下单等方法越来越长
创建逻辑分散对象初始化规则难统一

设计模式的价值是识别变化点,并把变化隔离在合适的位置。

mermaid
flowchart TD
    A["发现变化点"] --> B{"变化是否真实稳定"}
    B -- "否" --> C["先保持简单"]
    B -- "是" --> D["选择合适模式"]
    D --> E["隔离变化"]
    E --> F["让新增功能少改老代码"]

分类

mermaid
flowchart TD
    A["设计模式"] --> B["创建型<br/>解决对象创建"]
    A --> C["结构型<br/>解决对象组合"]
    A --> D["行为型<br/>解决行为协作"]
    B --> E["单例 / 工厂 / 建造者"]
    C --> F["代理 / 装饰器 / 适配器"]
    D --> G["策略 / 观察者 / 模板方法"]

学习路线

学习设计模式建议按“为什么要拆 -> 拆到哪里 -> 不拆会怎样 -> 拆过头会怎样”理解,不要先背类图。

建议先读总课程页:

  1. 从零到生产级掌握
  2. 从零到精通验收清单
  3. 商业场景训练营
  4. 设计模式面试题
mermaid
flowchart TD
    A["先识别变化点"] --> B["创建型:对象怎么创建"]
    B --> C["结构型:对象怎么组合增强"]
    C --> D["行为型:流程和规则怎么协作"]
    D --> E["结合 Spring / JDK / 商业场景"]
    E --> F["最后看面试表达"]

本目录

模式解决什么问题页面
总验收判断什么时候该用、怎么用、用错怎么排查从零到精通验收清单
单例模式全局唯一对象和共享资源管理单例模式
工厂模式封装对象创建和选择逻辑工厂模式
建造者模式分步骤构建复杂对象,避免长构造器建造者模式
代理模式在访问目标对象前后统一增强或控制代理模式
装饰器模式不修改原类,动态叠加能力装饰器模式
适配器模式把不兼容接口转换成统一接口适配器模式
策略模式多种算法或业务规则切换策略模式
观察者模式一对多事件通知和扩展观察者模式
模板方法模式固定流程骨架,开放变化步骤模板方法模式
责任链模式多个处理器按顺序处理请求责任链模式

怎么选择模式

mermaid
flowchart TD
    A["遇到设计问题"] --> B{"问题是对象创建吗"}
    B -- "是" --> C["工厂 / 单例 / 建造者"]
    B -- "否" --> D{"问题是功能增强或访问控制吗"}
    D -- "是" --> E["代理 / 装饰器 / 适配器"]
    D -- "否" --> F{"问题是多种规则切换吗"}
    F -- "是" --> G["策略"]
    F -- "否" --> H{"问题是一件事后通知多方吗"}
    H -- "是" --> I["观察者"]
    H -- "否" --> J{"问题是固定流程或校验链吗"}
    J -- "固定流程" --> K["模板方法"]
    J -- "校验链" --> L["责任链"]
    J -- "都不是" --> M["先保持简单"]

选择模式时要先问:

  1. 变化点是否真实存在。
  2. 变化是否会持续发生。
  3. 抽象后是否减少修改面。
  4. 新人是否能快速理解。
  5. 测试是否更容易。

Demo:策略模式消除 if/else

折扣规则会持续扩展时,可以用策略模式。

java
public interface DiscountStrategy {
    String type();

    BigDecimal discount(BigDecimal originPrice);
}
java
@Component
public class VipDiscountStrategy implements DiscountStrategy {
    @Override
    public String type() {
        return "VIP";
    }

    @Override
    public BigDecimal discount(BigDecimal originPrice) {
        return originPrice.multiply(new BigDecimal("0.8"));
    }
}
java
@Service
public class DiscountService {
    private final Map<String, DiscountStrategy> strategyMap;

    public DiscountService(List<DiscountStrategy> strategies) {
        this.strategyMap = strategies.stream()
            .collect(Collectors.toMap(DiscountStrategy::type, strategy -> strategy));
    }

    public BigDecimal calculate(String memberType, BigDecimal originPrice) {
        DiscountStrategy strategy = strategyMap.get(memberType);
        if (strategy == null) {
            return originPrice;
        }
        return strategy.discount(originPrice);
    }
}

这个 Demo 体现了设计模式的使用边界:当折扣规则确实会扩展时,策略模式能隔离变化;如果只有一个规则,就没必要提前抽象。

不要过度设计

设计模式不是越多越好。简单业务直接写清楚即可,只有当变化点明确、重复逻辑明显、扩展压力真实存在时,再考虑引入模式。

过度设计表现后果
一两个实现也抽接口、工厂、策略文件变多,跳转成本上升
业务还不稳定就抽象抽象很快失效,反而难改
为了模式而模式代码看起来高级,实际更难维护
所有逻辑都事件化调用链分散,排查困难

学习方法

每个模式都按以下顺序理解:

  1. 它解决什么问题。
  2. 如果不用会怎样。
  3. 它的角色有哪些。
  4. 它的流程是什么。
  5. 它带来什么好处。
  6. 它有什么代价。
  7. 在 Spring、JDK 或业务中哪里见过。

商业项目常见组合

场景常见组合为什么
支付方式选择工厂 + 策略工厂找到支付策略,策略执行支付
订单创建校验责任链 + 策略校验链控制顺序,不同业务规则独立
数据导入模板方法 + 责任链模板固定导入流程,责任链做校验
第三方医院接口适配器 + 策略适配器屏蔽协议差异,策略选择医院实现
Spring 事务代理模式方法调用经过代理,统一开启提交回滚
Java IO装饰器模式一层层包装输入输出能力

面试回答总模板

text
我理解设计模式不是为了套类图,而是为了隔离稳定变化点。比如同一业务有多种算法时用策略,创建实现类变化时用工厂,复杂对象参数多时用建造者,横切增强用代理,第三方接口不兼容用适配器,固定流程开放步骤用模板方法,多校验步骤按顺序执行用责任链。使用时也要避免过度设计,只有变化真实存在、抽象能减少修改面时才引入。

设计模式不是背 UML,而是训练判断力:看到变化点,能选择合适的隔离方式;看到简单问题,也能克制不乱抽象。