学了6种设计模式却写出了同事看不懂的代码——SOLID五原则反例对照,治好了我的"过度设计"

设计模式:GoF 经典 + SOLID 原则实战

学了单例工厂策略,面试能画 UML------但写代码时还是不知道该用哪个。直到有一次 code review,同事说我写的"策略模式"把 3 个 if-else 变成了 5 个类、2 个接口、1 个工厂------过度设计,为了"看起来高级"而不是"解决问题"。后来我用 SOLID 五原则做了一组反例对照表,每次写完代码过一遍,判断标准一下子清晰了。这篇文章把这张表给你。
阅读约 12 分钟 | 系列第 15/17 篇


一、设计模式概述

1.1 三大分类

类型 说明 典型模式
创建型 隐藏对象创建逻辑,降低客户端与具体类的耦合 单例、简单工厂、工厂方法、抽象工厂、建造者
结构型 处理类或对象的组合,获得更灵活的结构 适配器、装饰器、代理、组合、外观
行为型 描述类或对象之间的交互和通信模式 观察者、策略、模板方法、状态、发布-订阅

1.2 使用原则

  • 理解模式的本质和适用场景比记住定义更重要
  • 避免过度设计------拒绝不成熟的抽象同样重要
  • 模式不是万能的,根据具体场景决定是否使用

二、SOLID 设计原则

设计模式是"术",SOLID 是"道"。不理解 SOLID 就很难判断一个模式用对了还是用错了。

2.1 单一职责原则(SRP)

定义:一个类或模块应有且仅有一个改变的理由。

核心价值:可读性(一眼看清这个类做什么)+ 可测试性(职责单一→Mock 简单)+ 可复用性(小而聚焦的组件更容易被复用)。

2.2 开放-封闭原则(OCP)

定义:对扩展开放,对修改封闭。应通过增加新代码来扩展功能,而非修改现有代码。

实现方式:抽象与接口(定义抽象类或接口描述共同行为)+ 设计模式(策略模式、装饰者模式、工厂方法模式天然支持 OCP)。

2.3 里氏替换原则(LSP)

定义:子类对象必须能够替换父类对象,而不破坏程序的正确性。

违反 LSP 的典型场景

  • 子类重写父类方法但不保持父类契约(如 Rectangle.setWidth/setHeight vs Square------正方形不能独立设置宽高)
  • 子类方法抛出父类未声明的异常
  • 子类修改了父类方法的语义

实践instanceof 判断子类类型往往暗示 LSP 被违反------如果调用方需要知道具体子类才能正确工作,说明子类不能完全替换父类。

2.4 接口隔离原则(ISP)

定义:客户端不应被迫依赖它不使用的方法。多个小接口优于一个大而全的接口。

java 复制代码
// ❌ 违反 ISP:一个臃肿的接口
interface Worker {
    void work();
    void eat();
    void sleep();
}

// ✅ 遵循 ISP:拆分为小接口
interface Workable { void work(); }
interface Eatable  { void eat();  }
interface Sleepable { void sleep(); }

// Robot 只需实现 Workable,无需无意义的 eat() 和 sleep()
class Robot implements Workable { ... }
class Human implements Workable, Eatable, Sleepable { ... }

2.5 依赖倒置原则(DIP)

定义:高层模块不应依赖低层模块,两者都应依赖抽象。抽象不应依赖细节,细节应依赖抽象。

java 复制代码
// ❌ 违反 DIP:高层直接依赖具体实现
class OrderService {
    private MySQLOrderRepository repository = new MySQLOrderRepository();
}

// ✅ 遵循 DIP:依赖注入接口
class OrderService {
    private final OrderRepository repository;  // 接口
    public OrderService(OrderRepository repository) { this.repository = repository; }
}

DIP 是 Spring IoC/DI 的理论根基------Spring 容器通过接口注入具体实现,使高层(OrderService)与底层(MySQLOrderRepository)解耦。当数据源从 MySQL 切换到 PostgreSQL,只需新建一个 Infrastructure 实现类,Domain 和 Application 层完全不感知。


三、创建型模式

3.1 单例模式

确保一个类只有一个实例,并提供全局访问点。四种写法:

饿汉模式(类加载时实例化,线程安全但无法延迟加载):

java 复制代码
public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();
    private EagerSingleton() {}
    public static EagerSingleton getInstance() { return instance; }
}

懒汉模式(首次获取时实例化,需处理线程安全):

java 复制代码
public class LazySingleton {
    private static volatile LazySingleton instance;
    private LazySingleton() {}
    public static LazySingleton getInstance() {
        if (instance == null) {
            synchronized (LazySingleton.class) {
                if (instance == null) instance = new LazySingleton();
            }
        }
        return instance;
    }
}

枚举实现(推荐):天然线程安全 + 防反射攻击 + 防反序列化破坏。

java 复制代码
public enum EnumSingleton {
    INSTANCE;
    public void doSomething() { ... }
}
// 使用:EnumSingleton.INSTANCE.doSomething();

3.2 简单工厂模式

定义一个类专门负责创建其他类的实例,根据参数不同返回不同类的实例。

java 复制代码
public class OperationFactory {
    public static Operation createOperation(String operate) {
        switch (operate) {
            case "+": return new OperationAdd();
            case "-": return new OperationSub();
            default: return null;
        }
    }
}

适用 :产品种类较少且不会频繁增加。缺点:新增产品需修改工厂类 switch-case,违反 OCP。

3.3 工厂方法模式

定义一个创建对象的接口,但由子类决定实例化哪个类。

java 复制代码
public interface IOperationFactory {
    Operation getOperation();
}
public class FactoryAdd implements IOperationFactory {
    @Override
    public Operation getOperation() { return new OperationAdd(); }
}

与简单工厂的关键区别:新增产品不需要修改现有工厂代码------符合 OCP。

3.4 抽象工厂模式

提供接口用于创建一系列相关对象(产品族),而不需要指定具体类。

与工厂方法的区别 :工厂方法关注创建单个对象 ,抽象工厂关注创建一系列相关对象。例如:跨平台 GUI------Windows 工厂生产 WindowsButton + WindowsTextField,Mac 工厂生产 MacButton + MacTextField。

3.5 建造者模式

将一个复杂对象的构建与表示分离,使同样的构建过程可以创建不同的表示。适用:构造函数参数过多(>4 个)、需要链式调用逐步设置属性。

java 复制代码
Computer computer = new Computer.Builder()
    .cpu("Intel i9")
    .memory("32GB")
    .storage("1TB SSD")
    .build();
模式 核心问题 关键区别
简单工厂 创建哪种产品 switch-case 集中创建
工厂方法 哪个工厂创建产品 延迟到子类决定,符合 OCP
抽象工厂 创建一系列相关产品 产品族,工厂生产多个相关对象
建造者 如何一步步构建复杂对象 链式调用,参数可选

四、结构型模式

4.1 适配器模式

目的:将不兼容的接口转换为客户端期望的接口,使原本不兼容的类可以一起工作。

JDK 实例java.util.Arrays#asList()------将数组适配为 List 接口。InputStreamReader------将字节流 InputStream 适配为字符流 Reader。

4.2 装饰器模式

目的:动态地给对象添加额外职责,比继承更灵活。装饰器与目标对象实现同一接口,运行时"包装"目标并在调用前后插入额外行为。

JDK 实例BufferedReader(new FileReader(...))------为字符流添加缓冲能力。Java IO 流体系(InputStream → BufferedInputStream → DataInputStream)是装饰器模式的经典应用。

4.3 代理模式

目的:为另一个对象提供替身或占位符以控制访问------代理对象持有目标对象的引用并进行访问控制。

Spring 实例 :AOP 动态代理(JDK 动态代理 / CGLIB),@Transactional 注解的方法通过代理实现事务管理。

适配器 vs 装饰器 vs 代理:都是"包一层",区别在哪?

模式 目的 接口变化 关注点
适配器 接口转换------让不兼容的能一起工作 接口改变 兼容性
装饰器 功能增强------动态添加行为 接口不变 扩展性
代理 访问控制------控制对目标对象的访问 接口不变 控制

五、行为型模式

5.1 观察者模式

目的:定义一对多依赖关系,当被观察对象(Subject)状态改变时,所有依赖它的观察者(Observer)自动收到通知。

核心角色 :Subject(维护观察者列表 + 通知逻辑)、Observer(定义更新接口)。JDK 实例java.util.EventListener + java.util.Observer(已废弃但仍可理解模式)。

与发布-订阅的区别:观察者模式中 Subject 和 Observer 直接交互(知道彼此存在);发布-订阅通过 Broker/Topic 解耦------发布者和订阅者完全不知道彼此(如 MQ 的消息驱动)。

5.2 策略模式

目的:定义一系列算法,把它们分别封装,并使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端。

java 复制代码
// 策略接口
interface PaymentStrategy {
    void pay(BigDecimal amount);
}
class AliPay implements PaymentStrategy { ... }
class WechatPay implements PaymentStrategy { ... }

// 上下文------持有策略引用,运行时切换
class PaymentContext {
    private PaymentStrategy strategy;
    public void setStrategy(PaymentStrategy s) { this.strategy = s; }
    public void executePay(BigDecimal amount) { strategy.pay(amount); }
}

JDK 实例Comparator<T> 接口------不同的排序策略通过 Collections.sort(list, comparator) 运行时注入。ThreadRunnable 也是策略模式的体现。

策略 vs 状态 :策略模式是客户端主动切换 算法("我要用支付宝付款"),状态模式是状态自动驱动行为变更(交通灯:绿→黄→红自动流转)。

5.3 模板方法模式

目的:定义算法骨架,将某些步骤延迟到子类实现。子类可以不改变算法结构而重定义特定步骤。

Spring 实例JdbcTemplatequery() 方法------获取连接→执行查询→映射结果→释放连接是固定骨架,SQL 和行映射器(RowMapper)由调用方提供。AbstractApplicationContext.refresh() 定义容器启动的完整步骤,各子类实现特定步骤。


六、Spring 框架中的设计模式全景

Spring 是目前 Java 生态中应用设计模式最多的框架之一。理解以下映射关系,面试时能清晰展示"我知道它用了什么模式,为什么会这样设计":

设计模式 Spring 中的应用 对应组件
单例 Bean 默认 singleton scope @Service@Repository 默认单例
工厂方法 BeanFactory / ApplicationContext context.getBean("userService")
代理 AOP 事务管理 @Transactional 通过代理实现
模板方法 数据访问模板 JdbcTemplateRestTemplate
观察者 Spring Event @EventListener + ApplicationEventPublisher
策略 资源加载 ResourceLoader 的不同实现
适配器 MVC 处理器适配 HandlerAdapter 适配不同 Controller
依赖注入(DIP 的实践) IoC 容器 @Autowired 注入接口,运行时绑定实现

核心要点回顾

SOLID 五原则是设计模式的"道"------SRP 让类的职责聚焦、OCP 让系统对扩展开放对修改封闭、LSP 保证子类可以无缝替换父类、ISP 避免臃肿接口强制实现无用方法、DIP 通过"依赖抽象而非具体"成为 Spring IoC 的理论根基。

创建型 四种模式各有分工:单例(全局唯一实例,枚举为最优实现)、工厂三兄弟(简单工厂集中创建→工厂方法符合 OCP→抽象工厂创建产品族)、建造者(链式构建复杂对象)。结构型三种"包装"模式目的截然不同:适配器改变接口保证兼容、装饰器不变接口增强功能、代理控制访问------Java IO 流体系是装饰器的经典应用,Spring AOP 通过代理实现事务管理。

行为型关注对象间的交互:观察者模式通过 Subject→Observer 一对多通知、策略模式在运行时切换算法消除 if-else(JDK 的 Comparator 是典范)、模板方法定义算法骨架让子类填空(JdbcTemplate 是 Spring 中的经典案例)。


设计模式不是学得越多越好------关键是 SOLID 五原则那杆秤,知道"什么时候不用"。收藏这份反例对照表,每次 code review 时翻出来对照。
上一篇 :《容器与K8s》 | 下一篇 :《分库分表实战》 系列合集掘金Java合集

相关推荐
workflower4 小时前
案例:大模型驱动的“发现- 研判- 整改- 验证”全闭环治理体系
人工智能·深度学习·机器学习·设计模式·机器人
董员外6 小时前
RAG 系统进化论(九):RAG 评测,怎样证明系统真的变好了?
人工智能·设计模式·程序员
西安小哥7 小时前
AI时代技术工程师的"道法术器势":学习图谱、成长规划与角色重塑
人工智能·设计模式·程序员
莫得感情 o9 小时前
设计模式 08 · 装饰器模式
设计模式·装饰器模式
董员外1 天前
RAG 系统进化论(八):Agentic RAG(智能体式 RAG),从回答问题到完成任务
人工智能·后端·设计模式
董员外1 天前
RAG 系统进化论(七):Multimodal RAG(多模态 RAG),当知识存在于表格、图片和页面中
人工智能·后端·设计模式
大山同学1 天前
第 2 篇(03-04 章):代理设计原则与工具使用设计模式
设计模式
啦啦啦啦啦zzzz2 天前
设计模式:桥接模式和组合模式
c++·设计模式·组合模式·桥接模式
莫得感情 o2 天前
设计模式 03 · 工厂方法模式
设计模式·工厂方法模式