Skip to content

设计模式

工程实践 ⭐⭐ 中级 🔥 高频

💡 核心要点

设计模式是面试高频考点。重点不是背 23 种模式,而是理解 SOLID 原则、能说清每个模式解决什么问题、在 Spring/项目中的实际应用。


核心概念

SOLID 原则

原则一句话定义违反时的症状
S — 单一职责一个类只负责一件事一个类改动频繁、牵一发而动全身
O — 开闭原则对扩展开放,对修改关闭每加一种新类型就要改 if-else
L — 里氏替换子类可以替换父类重写父类方法后调用方行为异常
I — 接口隔离接口小而专,不强迫实现不需要的方法实现类有大量空方法
D — 依赖倒置依赖抽象而非具体实现换一个实现要改大量代码

23 种模式分类概览

类型模式
创建型单例、工厂方法、抽象工厂、建造者、原型
结构型代理、适配器、装饰器、桥接、外观、组合、享元
行为型策略、观察者、模板方法、责任链、命令、状态、备忘录、迭代器、中介者、访问者、解释器

面试重点模式

1. 单例模式

一句话定义:保证一个类在 JVM 中只有一个实例,并提供全局访问点。

结构关系:类持有自身静态实例,构造器私有,通过静态方法对外提供唯一实例。外部无法 new,只能通过 getInstance() 获取。

java
// DCL(Double-Checked Locking)推荐写法
public class ConfigManager {
    // volatile 禁止指令重排:new 操作分三步(分配内存、初始化对象、赋值引用),
    // 重排后其他线程可能拿到尚未初始化完成的对象
    private static volatile ConfigManager instance;

    private final Map<String, String> config = new HashMap<>();

    private ConfigManager() {
        // 加载配置
        config.put("timeout", "5000");
    }

    public static ConfigManager getInstance() {
        if (instance == null) {                      // 第一次检查:避免每次加锁
            synchronized (ConfigManager.class) {
                if (instance == null) {              // 第二次检查:防止重复创建
                    instance = new ConfigManager();
                }
            }
        }
        return instance;
    }

    public String get(String key) {
        return config.get(key);
    }
}

Spring 应用:Spring Bean 默认作用域为 singleton,容器负责管理单例生命周期,无需手动实现。连接池(HikariDataSource)也是典型单例。

面试追问:为什么要加 volatilenew 操作在 JVM 层面分三步:分配内存 → 初始化对象 → 将引用赋值给变量。指令重排可能导致步骤二和三互换,另一个线程在第一次检查时拿到非 null 但未初始化完成的对象,volatile 通过内存屏障禁止这种重排。


2. 工厂方法模式

一句话定义:将对象的创建逻辑封装到工厂,调用者只依赖接口,不依赖具体实现类。

结构关系:定义工厂接口,每个具体工厂负责创建一种具体产品。新增产品只需新增工厂类,无需修改已有代码,符合开闭原则。

java
// 产品接口
public interface Payment {
    void pay(java.math.BigDecimal amount);
}

// 具体产品
public class AlipayPayment implements Payment {
    public void pay(java.math.BigDecimal amount) {
        System.out.println("支付宝支付:" + amount);
    }
}

public class WechatPayment implements Payment {
    public void pay(java.math.BigDecimal amount) {
        System.out.println("微信支付:" + amount);
    }
}

// 工厂接口
public interface PaymentFactory {
    Payment create();
}

// 具体工厂:新增支付方式只需新增工厂类,不修改已有代码
public class AlipayFactory implements PaymentFactory {
    public Payment create() { return new AlipayPayment(); }
}

public class WechatFactory implements PaymentFactory {
    public Payment create() { return new WechatPayment(); }
}

// 调用方
PaymentFactory factory = new AlipayFactory();
Payment payment = factory.create();
payment.pay(new java.math.BigDecimal("99.00"));

Spring 应用BeanFactory 是工厂方法模式的典型实现;FactoryBean 接口允许自定义 Bean 的创建逻辑,getObject() 即工厂方法。

面试追问:工厂方法 vs 抽象工厂?工厂方法针对单一产品维度,每个工厂创建一种产品;抽象工厂针对产品族,一个工厂可以创建多个相关联的产品(如 UI 组件工厂同时创建按钮和文本框)。


3. 建造者模式

一句话定义:分步骤构建复杂对象,将构建过程与表示分离,支持链式调用,避免大量构造器重载。

结构关系:Builder 持有目标对象的各个参数,每个 setter 返回 this 支持链式调用,最终 build() 返回不可变的完整对象。

java
public class UserProfile {
    private final Long id;
    private final String name;
    private final String email;
    private final String phone;   // 可选
    private final String avatar;  // 可选

    private UserProfile(Builder builder) {
        this.id     = builder.id;
        this.name   = builder.name;
        this.email  = builder.email;
        this.phone  = builder.phone;
        this.avatar = builder.avatar;
    }

    public static class Builder {
        private Long id;
        private String name;
        private String email;
        private String phone;
        private String avatar;

        public Builder id(Long id)         { this.id = id;         return this; }
        public Builder name(String name)   { this.name = name;     return this; }
        public Builder email(String email) { this.email = email;   return this; }
        public Builder phone(String phone) { this.phone = phone;   return this; }
        public Builder avatar(String url)  { this.avatar = url;    return this; }

        public UserProfile build() {
            if (id == null || name == null) throw new IllegalStateException("id 和 name 必填");
            return new UserProfile(this);
        }
    }
}

// 使用:链式构建,只传必要参数
UserProfile user = new UserProfile.Builder()
    .id(1L)
    .name("张三")
    .email("zhangsan@example.com")
    .build();

Spring 应用StringBuilderUriComponentsBuilderBeanDefinitionBuilder;Lombok @Builder 注解自动生成完整 Builder 代码;MyBatis-Plus LambdaQueryWrapper 链式查询也体现建造者思想。

面试追问:建造者 vs 工厂?工厂关注"创建哪种产品"(对象类型的选择),建造者关注"如何一步步构建产品"(复杂对象的组装过程)。参数超过 4 个且有可选参数时优先考虑建造者。


4. 代理模式

一句话定义:为目标对象提供代理,在不修改原有代码的前提下,在方法调用前后织入增强逻辑(日志、事务、权限)。

结构关系:代理类与目标类实现相同接口,代理持有目标对象引用,方法调用时委托给目标执行,并在前后添加增强逻辑。

java
// JDK 动态代理(目标类必须实现接口)
public interface OrderService {
    void createOrder(String orderId);
}

public class OrderServiceImpl implements OrderService {
    public void createOrder(String orderId) {
        System.out.println("创建订单:" + orderId);
    }
}

// 通用日志代理工厂
public class LoggingProxyFactory {
    public static <T> T getProxy(T target) {
        return (T) java.lang.reflect.Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            (proxy, method, args) -> {
                long start = System.currentTimeMillis();
                System.out.println("调用方法:" + method.getName());
                Object result = method.invoke(target, args);
                long cost = System.currentTimeMillis() - start;
                System.out.println("耗时:" + cost + "ms");
                return result;
            }
        );
    }
}

// 使用
OrderService proxy = LoggingProxyFactory.getProxy(new OrderServiceImpl());
proxy.createOrder("ORDER-001");

Spring 应用:Spring AOP 基于代理实现;有接口时默认用 JDK 动态代理,无接口时用 CGLIB(字节码子类)。@Transactional@Async@Cacheable 均依赖代理,因此同类内部调用会导致这些注解失效。

面试追问:JDK 动态代理 vs CGLIB?JDK 基于接口 + 反射,要求目标类实现接口;CGLIB 基于继承 + ASM 字节码生成,无需接口但不能代理 final 类/方法。Spring Boot 2.x 起默认使用 CGLIB。


5. 策略模式

一句话定义:将算法族封装成独立策略类,运行时按需切换,消除大量 if-else。

结构关系:定义策略接口,每种算法实现为独立策略类;Context 持有策略引用,将执行委托给策略。新增算法只需新增策略类。

java
// 策略接口
public interface DiscountStrategy {
    java.math.BigDecimal calculate(java.math.BigDecimal price);
}

// 具体策略:用 @Component("key") 注册,key 即业务类型标识
@org.springframework.stereotype.Component("vip")
public class VipDiscount implements DiscountStrategy {
    public java.math.BigDecimal calculate(java.math.BigDecimal price) {
        return price.multiply(new java.math.BigDecimal("0.9")); // 九折
    }
}

@org.springframework.stereotype.Component("svip")
public class SvipDiscount implements DiscountStrategy {
    public java.math.BigDecimal calculate(java.math.BigDecimal price) {
        return price.multiply(new java.math.BigDecimal("0.8")); // 八折
    }
}

// Context:用 Map 注入所有策略,彻底消除 if-else
@org.springframework.stereotype.Service
public class PriceService {
    // Spring 自动将所有 DiscountStrategy Bean 注入,key = Bean name
    @org.springframework.beans.factory.annotation.Autowired
    private java.util.Map<String, DiscountStrategy> strategyMap;

    public java.math.BigDecimal getPrice(String userType, java.math.BigDecimal price) {
        DiscountStrategy strategy = strategyMap.get(userType);
        if (strategy == null) throw new IllegalArgumentException("未知用户类型:" + userType);
        return strategy.calculate(price);
    }
}

Spring 应用Comparator 是策略接口的典型例子;ResourceLoaderHandlerMapping 也使用策略模式。项目中用 Map<String, Strategy> + @Component 是消除 if-else 最常见的落地方案。

面试追问:策略 vs 状态?策略模式的切换由客户端主动触发,算法之间无关联;状态模式的切换由状态对象自身触发,状态之间有转换关系,侧重对象行为随状态变化而变化。


6. 观察者模式

一句话定义:定义对象间一对多依赖,当被观察者状态变化时,自动通知所有观察者,实现发布方与订阅方解耦。

结构关系:Subject(被观察者)维护 Observer 列表,状态变更时遍历通知;观察者实现统一监听接口,互不干扰。

java
// Spring 事件驱动(观察者模式的标准 Spring 实现)

// 1. 定义事件(携带业务数据)
public class OrderCreatedEvent extends org.springframework.context.ApplicationEvent {
    private final Long orderId;
    private final java.math.BigDecimal amount;

    public OrderCreatedEvent(Object source, Long orderId, java.math.BigDecimal amount) {
        super(source);
        this.orderId = orderId;
        this.amount  = amount;
    }
    public Long getOrderId()                   { return orderId; }
    public java.math.BigDecimal getAmount()    { return amount; }
}

// 2. 发布事件(发布方只关心发布,不关心谁在监听)
@org.springframework.stereotype.Service
public class OrderService {
    @org.springframework.beans.factory.annotation.Autowired
    private org.springframework.context.ApplicationEventPublisher publisher;

    public void createOrder(Long orderId, java.math.BigDecimal amount) {
        // 核心业务逻辑 ...
        publisher.publishEvent(new OrderCreatedEvent(this, orderId, amount));
    }
}

// 3. 多个监听器独立处理,互不影响
@org.springframework.stereotype.Component
public class InventoryListener {
    @org.springframework.context.event.EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        System.out.println("扣减库存,订单:" + event.getOrderId());
    }
}

@org.springframework.stereotype.Component
public class PointsListener {
    @org.springframework.context.event.EventListener
    @org.springframework.scheduling.annotation.Async
    public void onOrderCreated(OrderCreatedEvent event) {
        System.out.println("增加积分,金额:" + event.getAmount());
    }
}

Spring 应用ApplicationEvent + @EventListener 是标准实现;ContextRefreshedEventContextClosedEvent 是容器内置事件。@Async 可将监听器改为异步,不阻塞主业务流程。

面试追问:观察者 vs 发布订阅?观察者模式中,Subject 直接持有并通知 Observer,两者存在直接依赖;发布订阅模式通过消息中间件(如 Kafka、RabbitMQ)解耦,发布方和订阅方完全不知道彼此的存在,是观察者模式的进一步解耦。


7. 模板方法模式

一句话定义:在抽象类中定义算法骨架(固定流程),将可变步骤延迟到子类实现,子类不改变整体结构。

结构关系:抽象类定义 final 模板方法(骨架)+ 抽象方法(子类必须实现)+ 钩子方法(子类可选覆盖)。父类控制流程,子类填充细节。

java
public abstract class DataExporter {
    // 模板方法:定义固定导出流程,final 禁止子类覆盖
    public final void export(String destination) {
        connect();
        java.util.List<String> data = fetchData();    // 子类实现:从不同数据源取数
        if (needFilter()) {                            // 钩子方法:子类可选择是否过滤
            data = filter(data);
        }
        writeOutput(data, destination);               // 子类实现:写入不同格式
        close();
    }

    private void connect() { System.out.println("建立数据源连接"); }
    private void close()   { System.out.println("关闭连接,释放资源"); }

    // 抽象方法:子类必须实现
    protected abstract java.util.List<String> fetchData();
    protected abstract void writeOutput(java.util.List<String> data, String dest);

    // 钩子方法:子类可选覆盖,默认不过滤
    protected boolean needFilter()                   { return false; }
    protected java.util.List<String> filter(java.util.List<String> d) { return d; }
}

// 具体子类:只关注自己负责的步骤
public class CsvExporter extends DataExporter {
    protected java.util.List<String> fetchData() {
        return java.util.Arrays.asList("row1,a", "row2,b");
    }
    protected void writeOutput(java.util.List<String> data, String dest) {
        System.out.println("写入 CSV 文件 [" + dest + "]:" + data);
    }
}

Spring 应用JdbcTemplateRestTemplateRedisTemplate 均是模板方法模式的典型应用,封装了样板代码(连接/释放),开放数据处理逻辑;AbstractApplicationContext#refresh() 定义了容器启动的完整骨架。

面试追问:模板方法 vs 策略?模板方法通过继承在编译时固定算法骨架,子类只能替换部分步骤;策略模式通过组合在运行时动态切换完整算法。模板方法适合"流程固定,局部可变";策略适合"算法整体可替换"。


8. 责任链模式

一句话定义:将请求沿着处理链传递,每个处理者决定是否处理请求及是否继续向下传递,实现请求发送者与处理者解耦。

结构关系:每个处理者持有下一个处理者的引用,形成链式结构。请求从链头进入,沿链传递直到被处理或到达链尾。

java
public abstract class RequestHandler {
    protected RequestHandler next;

    // 链式设置,返回 next 支持 a.setNext(b).setNext(c) 写法
    public RequestHandler setNext(RequestHandler next) {
        this.next = next;
        return next;
    }

    public abstract void handle(HttpRequest request);

    protected void passToNext(HttpRequest request) {
        if (next != null) next.handle(request);
    }
}

public class AuthHandler extends RequestHandler {
    public void handle(HttpRequest request) {
        if (!request.isAuthenticated()) {
            System.out.println("鉴权失败,拒绝请求:" + request.getPath());
            return; // 不继续传递
        }
        System.out.println("鉴权通过");
        passToNext(request);
    }
}

public class RateLimitHandler extends RequestHandler {
    public void handle(HttpRequest request) {
        if (request.isRateLimited()) {
            System.out.println("触发限流,拒绝请求");
            return;
        }
        System.out.println("限流检查通过");
        passToNext(request);
    }
}

public class BusinessHandler extends RequestHandler {
    public void handle(HttpRequest request) {
        System.out.println("处理业务逻辑:" + request.getPath());
    }
}

// 构建并使用责任链
RequestHandler auth = new AuthHandler();
auth.setNext(new RateLimitHandler()).setNext(new BusinessHandler());
auth.handle(request);

Spring 应用:Servlet FilterChaindoFilter 中调用 chain.doFilter() 传递请求);Spring Security SecurityFilterChain 由多个 Filter 按序组成;Spring MVC HandlerInterceptor 拦截器链;Netty ChannelPipeline

面试追问:哪些框架用了责任链?Servlet Filter、Spring Security Filter Chain、Spring MVC Interceptor、Dubbo Filter、Netty Pipeline、MyBatis Plugin(拦截器链)都是责任链模式的经典应用。

9. 状态模式(State Machine)

业务面试 Top 模式——任何"订单状态、审批流、工单生命周期"类系统都会用到。能写出干净的状态机代码,立刻区分初级和中级工程师。

一句话定义:把每个状态的行为封装为独立类,状态切换 = 替换对象引用,彻底消除巨型 switch(status)

典型场景:订单状态(待支付 → 已支付 → 已发货 → 已签收)、工单审批流、设备状态、游戏角色状态。

反模式:if-else / switch 的灾难
java
// ❌ 经典反模式:随业务增长会膨胀到几百行
public class Order {
    private String status;
    public void pay() {
        if ("PENDING".equals(status)) {
            // 校验金额、扣库存、改状态...
            status = "PAID";
        } else if ("PAID".equals(status)) {
            throw new IllegalStateException("已支付");
        } else if ("CANCELLED".equals(status)) {
            throw new IllegalStateException("已取消");
        } else if ("REFUNDING".equals(status)) {
            throw new IllegalStateException("退款中");
        }
        // ... 还有 ship() refund() cancel() 各自的 if-else
    }
}
状态模式重构
java
// 状态接口
public interface OrderState {
    void pay(OrderContext ctx);
    void ship(OrderContext ctx);
    void cancel(OrderContext ctx);
}

// 具体状态:每个 class 只负责自己状态下能做什么
@Component("pendingState")
public class PendingState implements OrderState {
    public void pay(OrderContext ctx)    { ctx.setState("paidState"); /* 扣库存 */ }
    public void ship(OrderContext ctx)   { throw new IllegalStateException("待支付不能发货"); }
    public void cancel(OrderContext ctx) { ctx.setState("cancelledState"); }
}

@Component("paidState")
public class PaidState implements OrderState {
    public void pay(OrderContext ctx)    { throw new IllegalStateException("已支付"); }
    public void ship(OrderContext ctx)   { ctx.setState("shippedState"); /* 通知物流 */ }
    public void cancel(OrderContext ctx) { ctx.setState("refundingState"); /* 触发退款 */ }
}

// 上下文:持有当前状态,对外暴露固定 API
@Component
public class OrderContext {
    @Autowired private Map<String, OrderState> stateMap;
    private OrderState current = stateMap.get("pendingState");

    public void pay()    { current.pay(this); }
    public void ship()   { current.ship(this); }
    public void cancel() { current.cancel(this); }
    public void setState(String name) { this.current = stateMap.get(name); }
}
状态模式 vs 策略模式
维度策略模式状态模式
本质算法可替换状态可流转
谁切换客户端显式选状态本身决定下一个状态
状态间是否互通各算法独立状态间相互引用、构成状态图
典型场景优惠/计费/算法切换订单/审批/工单生命周期
生产升级:状态机引擎(Spring StateMachine / Squirrel)
java
@Configuration
@EnableStateMachine
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<States, Events> {
    @Override
    public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception {
        transitions
            .withExternal()
                .source(States.PENDING).target(States.PAID).event(Events.PAY)
                .action(ctx -> { /* 扣库存 */ })
                .guard(ctx -> /* 校验金额 */ true)
            .and()
            .withExternal()
                .source(States.PAID).target(States.SHIPPED).event(Events.SHIP)
            .and()
            .withExternal()
                .source(States.PENDING).target(States.CANCELLED).event(Events.CANCEL);
    }
}

💡 何时手撸状态模式,何时上引擎

手撸足够:3-5 个状态、流转简单(单向为主);上引擎:10+ 状态、有分支/合并/并行、需要可视化、要持久化状态机实例。滴滴/美团/京东的订单核心都是上Spring StateMachine 或自研引擎

Spring 应用:Spring StateMachine 框架;Spring WebFlow(已淘汰);Activiti / Camunda 工作流引擎本质都是状态机。

面试加分点:能讲出"状态模式消除巨型 switch + 状态间显式定义转移 + 配合状态机引擎做生产级状态流转 + 状态持久化(每次切换记录到 DB 用于审计)"。


面试常问 & 怎么答

单例模式有几种实现方式?各自优缺点?

饿汉式(类加载时创建,线程安全但浪费资源)、懒汉式(需加锁)、DCL(双重检查+volatile)、静态内部类(推荐,延迟加载+线程安全)、枚举(最安全,防反射和序列化破坏)。

JDK 动态代理和 CGLIB 的区别?

JDK 动态代理基于接口 + 反射(Proxy.newProxyInstance),要求目标类实现接口。CGLIB 基于继承 + 字节码生成(ASM),不需要接口但不能代理 final 类/方法。Spring AOP 默认:有接口用 JDK,无接口用 CGLIB。

设计模式的六大原则?

SOLID 五原则 + 迪米特法则(最少知道原则:一个对象应当对其他对象有尽可能少的了解)。

你在项目中用过哪些设计模式?

回答模板:说场景 → 说用了什么模式 → 说解决了什么问题。例如:支付系统中使用策略模式封装不同支付方式,新增支付渠道只需实现接口,无需修改原有代码。


过度设计反例 5 大典型 — 必背警示

面试高频追问:"你见过什么设计模式滥用的反例?"——能讲出真实反例,比背 23 种模式更有说服力。简洁直接的代码 > 学院派的"教科书完美设计"。

反例 1:滥用单例 — 全局状态地狱

java
// ❌ 反例:把 UserService 做成单例,方便 "随处调用"
public class UserService {
    private static UserService instance;
    private Map<Long, User> userCache = new HashMap<>();   // ★ 全局状态

    public static UserService getInstance() { ... }
    public void saveUser(User u) { userCache.put(u.getId(), u); }
}

// 问题:
// 1. 单元测试无法 mock,每个测试都污染同一份 cache
// 2. 多线程下 HashMap 并发出 bug
// 3. 改造成分布式时改不动(每个 JVM 一份)

正确做法:用 Spring 容器的"单例 Bean"(实际上是 Spring 管理的单实例,不是 GoF 单例),方便依赖注入和测试 mock。业务代码里不要自己写 getInstance()

反例 2:策略模式只有 1 种实现

java
// ❌ 反例:为了"以后可扩展",给只有 1 种实现的逻辑套策略模式
public interface PaymentStrategy {
    void pay(BigDecimal amount);
}

public class AlipayStrategy implements PaymentStrategy { ... }   // ★ 只有这一个!

@Service
public class PaymentService {
    private final PaymentStrategy strategy;   // 永远只注入 Alipay
    public void pay(BigDecimal amount) { strategy.pay(amount); }
}

问题:"以后可能扩展"——这是过度设计的头号借口YAGNI 原则:You Aren't Gonna Need It,等真有第 2 种支付方式再重构成策略模式,不要提前设计。

反例 3:工厂的工厂的工厂

java
// ❌ 反例:抽象工厂 + 工厂方法 + 简单工厂 三层套娃
provider.getFactoryCreator().createProductFactory().createProduct();

正确做法:99% 场景用 Spring @Bean + @Conditional 或者简单工厂 + 注入 Map 一次解决:

java
@Service
public class ProductService {
    private final Map<String, ProductHandler> handlers;   // ★ Spring 自动注入所有实现

    public ProductService(List<ProductHandler> all) {
        this.handlers = all.stream()
            .collect(Collectors.toMap(ProductHandler::getType, Function.identity()));
    }

    public Product create(String type) {
        return handlers.get(type).create();
    }
}

反例 4:本地事件用消息队列重写

java
// ❌ 反例:同一 JVM 内的事件通知非要用 Kafka
@Service
public class OrderService {
    @Autowired KafkaTemplate kafka;
    public void createOrder(Order o) {
        save(o);
        kafka.send("order-created", o);   // ★ 5ms 操作变 50ms + 运维成本
    }
}

正确做法:单进程内用 Spring ApplicationEvent + @EventListener(同步)或 @Async(异步);跨服务才上 Kafka。

java
publisher.publishEvent(new OrderCreatedEvent(order));

@EventListener
@Async
public void handle(OrderCreatedEvent e) { sendEmail(e.getOrder()); }

反例 5:装饰器嵌套到看不懂

java
// ❌ 反例:5 层装饰器嵌套,出 bug 不知道哪一层抛的
InputStream in = new BufferedInputStream(
    new GZIPInputStream(
        new CipherInputStream(
            new Base64InputStream(
                new FileInputStream("data.txt")
            ), cipher)));

正确做法:3 层以内还能接受,更多层就该抽出独立方法或者用 Builder 模式链式构建。

模式滥用 vs 简洁代码 — 决策表

场景推荐不推荐
只有 1 种实现直接写死策略 / 工厂
本地事件@EventListenerKafka
简单对象创建new + 构造器抽象工厂
参数 < 4 个普通构造器Builder
2-3 种简单分支if/else 或 switch状态模式
类层级 > 3 层组合 over 继承继续套娃
可能扩展但还没需求先简单写提前抽象(YAGNI 违反)

Spring 官方推荐的"简化模式"

替代场景传统 GoFSpring 简化
单例双重检查锁@Component / @Service(Spring 管理)
工厂AbstractFactory@Bean + @Conditional + 注入 List/Map
观察者自定义 Subject/ObserverApplicationEventPublisher + @EventListener
模板方法抽象类 + 子类JdbcTemplate / RestTemplate
代理手写 JDK / CGLIB@Transactional / @Async / @Cacheable(AOP)
责任链自定义链表OncePerRequestFilter + Filter 链

黄金答题模板(必背)

面试官:你怎么看设计模式?

:设计模式是沟通工具和经验沉淀,不是 KPI。我会遵循 3 个原则: ① YAGNI:等真有 2 种以上场景再抽象,不要为"以后可能需要"提前设计; ② 优先用框架提供的简化版:Spring 的 @EventListener@Bean@Transactional 已经把 GoF 模式封装好了,业务代码不要自己造轮子; ③ 3 层以内能解决就别套娃:装饰器、工厂超过 3 层别人就看不懂了。 我见过最严重的反例是:本地事件通知硬上 Kafka,5ms 的操作变 50ms,还增加运维成本;以及为"以后可能支持"建的策略模式接口,半年后那个"可能"也没出现,反而让代码多了一层无意义的间接。最好的设计模式是没有模式,但读代码的人会自然说"这就是 XX 模式"


常见陷阱

陷阱症状正确做法
过度设计简单问题用复杂模式,代码难以理解先写直白代码,出现重复或扩展需求再引入模式
单例滥用所有东西都做成单例,全局状态难以测试只有真正需要全局唯一的才用单例
模式混淆策略和状态、工厂和建造者分不清理解每个模式解决的核心问题,而非记忆结构

看到什么就先想到这类

  • "全局唯一实例" → 单例
  • "创建不同子类对象" → 工厂
  • "多种可选参数构建" → 建造者
  • "动态增强/AOP" → 代理
  • "多种算法切换" → 策略
  • "事件通知/解耦" → 观察者
  • "固定流程+可变步骤" → 模板方法
  • "多级校验/过滤" → 责任链