Spring + 设计模式 总结+注意 避免走火入魔
鉴于设计模式文章更新完毕,总结一下
设计模式是什么?
源自建筑学家Christopher Alexander的工作,后被GoF:Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides引入软件开发领域,他们在1994年出版的《设计模式:可复用面向对象软件的基础》一书中系统化提出了23种经典设计模式
设计模式是软件工程领域中针对反复出现的设计问题所提炼出的一系列标准化、可重用的解决方案模板,它凝聚了全球软件开发专家数十年的集体智慧与实践经验,代表了行业公认的最佳工程实践。这些模式不是凭空产生的理论,而是从大量成功软件系统的架构设计中抽象出来的核心要素,经过长期验证能够有效提升代码的可维护性、扩展性和复用性
Spring 框架是企业级 Java 开发的基石,其设计和实现大量使用了设计模式,以实现高内聚、低耦合、可扩展和高可维护性的目标,这些模式在 Spring 中的实现(如 AOP、DI、事件机制)极大简化了开发,提高了代码质量。正确使用这些模式需要结合 Spring 的注解、配置和框架特性,遵循简单性和约定优于配置的原则
设计模式的核心概念
- 不是具体代码:设计模式是解决问题的思路和模板,不是可以直接拷贝粘贴的代码
- 经过验证:这些解决方案被反复使用并证明有效
- 语言无关:适用于各种面向对象编程语言
- 特定场景:每种模式解决特定类型的问题
为什么要用设计模式?
使用设计模式有以下几个主要原因:
-
提高代码可复用性:设计模式提供了经过验证的解决方案,可以在不同项目中复用,减少重复开发
-
增强代码可维护性:模式通常遵循标准化的结构,代码更清晰,易于理解和维护
-
提高开发效率:设计模式是成熟的解决方案,能快速解决常见问题,缩短开发时间
-
促进团队协作:设计模式是通用的设计语言,团队成员可以更快理解彼此的代码
-
应对复杂需求:模式帮助组织代码结构,更好地应对变化和扩展需求
-
降低出错风险:经过广泛验证的模式能减少设计缺陷和潜在 bug
总结来说,设计模式是软件工程中总结的最佳实践,能提升代码质量、效率和协作能力
反过来讲,如果不使用设计模式,可能会出现以下问题:
-
代码混乱:缺乏统一结构,代码可能变得杂乱无章,难以阅读和维护
-
重复开发:没有复用成熟方案,可能反复解决相同问题,浪费时间和资源
-
扩展困难:代码设计缺乏灵活性,面对需求变更或功能扩展时,修改成本高
-
维护成本高:非标准化的代码让新开发者难以理解,修复 bug 或添加功能耗时长
-
协作效率低:团队成员间缺乏共同的设计语言,沟通和代码审查效率降低
-
错误风险增加:没有经过验证的解决方案,可能引入设计缺陷或隐藏 bug
虽然不使用设计模式在小型或简单项目中可能影响不大,但在复杂或长期维护的项目中,缺乏设计模式往往会导致开发效率低下和质量问题
怎么用设计模式?
应用场景
一、创建型模式(Creational Patterns)
这些模式关注对象的创建过程,旨在提高对象创建的灵活性和效率
工厂方法(Factory Method)
- Spring 中的体现:
BeanFactory和ApplicationContext是工厂模式的典型实现,负责创建和管理 Bean- 具体实现类(如
ClassPathXmlApplicationContext)定义了 Bean 的创建方式
- 应用场景:
- 根据配置文件或注解动态创建不同类型的 Bean(如 XML 配置或
@Component扫描) - 示例:
getBean()方法根据 Bean 名称或类型返回实例 - 当需要创建的对象类型在运行时决定(如基于用户输入或配置)
- 需要解耦对象的创建和使用(如不同平台的UI组件:Windows按钮、Mac按钮)
- 典型案例:日志框架(如SLF4J选择具体日志实现)、数据库驱动(如JDBC连接不同数据库)
- 根据配置文件或注解动态创建不同类型的 Bean(如 XML 配置或
- 实现细节:
- Spring 通过依赖注入(DI)解耦了对象的创建和使用,工厂方法由容器自动执行
- 详细介绍:
抽象工厂(Abstract Factory)
- 应用场景:
- 需要创建一组相关或依赖的对象(如UI工具包中创建按钮、文本框、窗口,且风格一致,如Windows或Mac风格)
- 系统需要支持多种产品族(如不同数据库的连接、语句和事务管理)
- 典型案例:跨平台GUI框架、数据库访问层(如Hibernate支持多种数据库)
- Spring 中的体现:
- Spring 的
ApplicationContext提供了一组相关对象的创建(如 Bean、资源加载器、事件发布器) - 不同上下文(如
WebApplicationContext)提供特定环境的工厂实现
- Spring 的
- 实现细节:
- 容器通过配置(如 XML 或 Java Config)统一管理相关对象的创建
- 详细介绍:
单例(Singleton)
- 应用场景:
- 需要全局唯一实例(如日志管理器、配置文件管理器、数据库连接池)
- 管理共享资源(如线程池、缓存实例)
- 典型案例:Spring中的单例Bean、Java的Runtime类
- Spring 中的体现:
- Spring 的默认 Bean 作用域是
singleton,确保一个 Bean 定义在容器中只有一个实例,类似于单例模式 - 通过
ApplicationContext获取的 Bean 由容器管理,保证全局唯一性
- Spring 的默认 Bean 作用域是
- 实现细节:
- Spring 通过
BeanFactory和容器管理单例,而不是传统的静态变量实现,解决了线程安全和反射攻击问题 - 非单例作用域(如
prototype)可通过配置实现
- Spring 通过
- 详细介绍:
建造者(Builder)
- 应用场景:
- 构造复杂对象,需分步配置(如构建HTML文档、SQL查询)
- 需要创建不同配置的同一对象(如汉堡包的不同配料组合)
- 典型案例:StringBuilder、MyBatis的SQL构建、Lombok的@Builder注解
- Spring 中的体现:
- Spring Boot 的
SpringApplicationBuilder用于分步构建应用上下文 BeanDefinitionBuilder用于动态构造 Bean 定义
- Spring Boot 的
- 实现细节:
- 提供流式 API(如
SpringApplicationBuilder的.properties()方法),简化复杂对象的构造
- 提供流式 API(如
- 详细介绍:
原型(Prototype)
- 应用场景:
- 需要通过复制现有对象创建新对象(如深度克隆图形对象)
- 对象创建成本高,复制更高效(如复杂的游戏角色模板)
- 典型案例:Java的Cloneable接口、原型设计工具(如Figma中的组件复制)
- Spring 中的体现:
- Spring 支持
prototype作用域,每次请求 Bean 时创建新实例
- Spring 支持
- 实现细节:
- 通过
@Scope("prototype")配置,容器在每次getBean()时创建新实例
- 通过
- 详细介绍:
二、结构型模式(Structural Patterns)
这些模式关注类和对象的组合,优化系统结构,提高灵活性和可扩展性
适配器(Adapter)
- 应用场景:
- 将不兼容的接口转换为目标接口(如旧系统和新系统的集成)
- 统一不同第三方库的接口(如日志框架适配不同日志库)
- 典型案例:Java的InputStreamReader(将字节流适配为字符流)、Spring MVC的HandlerAdapter
- Spring 中的体现:
- Spring MVC 的
HandlerAdapter将不同类型的控制器(如@Controller、Servlet)适配为统一的处理流程 MessageConverter将不同格式(如 JSON、XML)适配为对象
- Spring MVC 的
- 实现细节:
HandlerAdapter接口定义标准处理方法,具体适配器(如RequestMappingHandlerAdapter)实现细节
- 详细介绍:
装饰者(Decorator)
- 应用场景:
- 动态为对象添加职责,而不修改其代码(如为咖啡添加奶泡、糖浆)
- 需要扩展功能且保持接口一致(如文件流的加密、压缩)
- 典型案例:Java的IO流(如BufferedInputStream)、Spring的AOP
- Spring 中的体现:
- Spring 的 AOP 也可以看作装饰者模式的变种,通过代理动态为目标对象添加功能
CacheManager和TransactionManager等组件通过包装增强功能
- 实现细节:
- 使用
Advisor和Advice定义切面逻辑,动态增强目标对象
- 使用
- 详细介绍:
代理(Proxy)
- 应用场景:
- 控制对对象的访问(如延迟加载、权限检查)
- 远程调用或虚拟对象(如RMI、Web服务的代理)
- 典型案例:Spring的AOP代理、Hibernate的懒加载代理、Java的动态代理
- 事务管理:
@Transactional注解通过代理自动管理事务 - 安全控制:Spring Security 使用代理拦截请求,验证权限
- Spring 中的体现:
- Spring AOP(面向切面编程)通过动态代理(基于 JDK 动态代理或 CGLIB)实现切面功能
- 代理对象拦截方法调用,添加横切关注点(如日志、事务、权限检查)
- 实现细节:
- Spring 的
ProxyFactory和DefaultAopProxyFactory生成代理对象 - 示例:
TransactionProxyFactoryBean为服务类生成事务代理
- Spring 的
- 详细介绍:
外观(Facade)
- 应用场景:
- 为复杂子系统提供简化的统一接口(如简化多模块交互)
- 降低客户端与子系统的耦合(如封装第三方库的复杂API)
- 典型案例:JDBC的DataSource接口、SLF4J的日志门面
- 简化 Bean 管理、依赖注入和环境配置
- Spring 中的体现:
ApplicationContext提供简化的门面接口,隐藏容器内部复杂性- Spring Boot 的自动配置机制为开发者提供简单的配置入口
- 实现细节:
- 通过
@SpringBootApplication和自动配置,开发者无需直接操作底层容器
- 通过
- 详细介绍:
桥接(Bridge)
- 应用场景:
- 将抽象与实现分离,支持两者独立变化(如形状和颜色的组合)
- 需要跨平台实现(如JDBC驱动与数据库的分离)
- 典型案例:Java的AWT(图形与平台实现分离)、JDBC驱动
- 支持不同资源类型或数据库的无缝切换
- Spring 中的体现:
- Spring 的
Resource抽象(如ClassPathResource、FileSystemResource)将资源访问与实现分离 DataSource接口与具体数据库驱动(如 MySQL、PostgreSQL)解耦
- Spring 的
- 实现细节:
- 通过接口(如
Resource)定义抽象,具体实现由子类提供
- 通过接口(如
- 详细介绍:
组合(Composite)
- 应用场景:
- 表示树形结构(如文件系统、组织架构)
- 统一处理单个对象和组合对象(如图形编辑器的形状和分组)
- 典型案例:DOM树、Swing的组件树、文件目录结构
- 管理复杂应用的模块化配置(如 Web 和数据层分开)
- Spring 中的体现:
- Spring 的
ApplicationContext形成树形结构,支持嵌套上下文(如父子容器)
- Spring 的
- 实现细节:
- 子上下文继承父上下文的 Bean 定义,形成层级结构
- 详细介绍:
享元(Flyweight)
- 应用场景:
- 需要共享大量细粒度对象以节省内存(如文字编辑器中的字符对象)
- 大量相似 Bean 的定义(如模板化的配置)
- 典型案例:Java的String常量池、游戏引擎中的纹理共享
- Spring 中的体现:
- Spring 的 Bean 定义(
BeanDefinition)是共享的轻量级对象,减少内存占用
- Spring 的 Bean 定义(
- 实现细节:
- 容器缓存
BeanDefinition,在需要时实例化
- 容器缓存
- 详细介绍:
三、行为型模式(Behavioral Patterns)
这些模式关注对象之间的交互和职责分配,优化通信和协作
观察者(Observer)
- 应用场景:
- 当一个对象状态变化需要通知其他对象(如UI更新、事件监听)
- 发布-订阅模型(如消息队列、事件总线)
- 响应容器事件(如上下文刷新、关闭)
@EventListener注解处理自定义事件- 典型案例:Java的Observable类、Spring的事件监听、GUI事件处理
- Spring 中的体现:
- Spring 的事件机制(如
ApplicationEvent和ApplicationListener)实现发布-订阅模型
- Spring 的事件机制(如
- 实现细节:
ApplicationEventPublisher发布事件,ApplicationListener接收并处理
- 详细介绍:
策略(Strategy)
- 应用场景:
- 需要动态切换算法或行为(如排序算法、支付方式)
- 避免大量条件语句(如不同折扣策略)
- 动态选择资源加载或认证方式
- 典型案例:Java的Comparator接口、Spring的Resource加载策略
- Spring 中的体现:
- Spring 的
Resource加载策略(如ClassPathResource、UrlResource) AuthenticationProvider支持多种认证策略(如 LDAP、数据库)
- Spring 的
- 实现细节:
- 通过接口(如
Resource)定义策略,具体实现由子类提供
- 通过接口(如
- 详细介绍:
命令(Command)
- 应用场景:
- 将请求封装为对象,支持撤销、重做(如文本编辑器的操作)
- 异步任务队列(如线程池的任务提交)
- 批量处理任务,支持事务和重试
- 典型案例:Swing的Action、Java的Runnable接口
- Spring 中的体现:
- Spring Batch 的
Job和Step封装任务为命令对象,支持执行和回滚
- Spring Batch 的
- 实现细节:
JobLauncher执行封装的任务命令
- 详细介绍:
模板方法(Template Method)
- 应用场景:
- 定义算法骨架,子类实现具体步骤(如数据处理的流程)
- 统一父类的通用逻辑(如Servlet的service方法)
- 简化数据库操作或 HTTP 请求
- 示例:
JdbcTemplate的query方法定义查询流程
- Spring 中的体现:
JdbcTemplate、RestTemplate等封装通用流程,子类提供具体实现
- 实现细节:
- 父类定义算法骨架,子类通过回调(如
RowMapper)实现细节
- 父类定义算法骨架,子类通过回调(如
- 详细介绍:
迭代器(Iterator)
- 应用场景:
- 遍历集合而不暴露其内部结构(如数组、链表、树)
- 支持多种遍历方式(如正序、倒序)
- 典型案例:Java的Iterator接口、Python的forTterator
- 遍历容器中的 Bean 或配置
- Spring 中的体现:
- Spring 的
BeanDefinitionRegistry支持迭代 Bean 定义
- Spring 的
- 实现细节:
- 通过
ListableBeanFactory的getBeanNamesForType等方法实现
- 通过
- 详细介绍:
责任链(Chain of Responsibility)
- 应用场景:
- 按顺序传递请求直到被处理(如日志级别过滤、权限检查)
- 避免请求者和处理器耦合(如Web过滤器链)
- 典型案例:Servlet过滤器、Spring Security的认证链
- 按序执行认证、授权、日志等逻辑
- Spring 中的体现:
- Spring Security 的
FilterChain按顺序处理请求 - Spring MVC 的
HandlerInterceptor链处理请求
- Spring Security 的
- 实现细节:
- 每个过滤器决定是否继续传递请求
- 详细介绍:
状态(State)
- 应用场景:
- 对象行为随状态变化(如订单的待支付、已发货、已完成)
- 替代复杂的条件语句(如游戏角色的不同状态)
- 典型案例:TCP连接状态机、工作流引擎
- 工作流或业务流程的状态管理
- Spring 中的体现:
- Spring Statemachine 管理复杂状态转换(如订单状态:待支付 → 已支付 → 已完成)
- 实现细节:
- 定义状态和转换规则,通过事件触发状态变更
- 详细介绍:
访问者(Visitor)
- 应用场景:
- 将操作与对象结构分离(如对复杂对象结构的遍历和处理)
- 需要为对象添加新操作而不修改其类(如报表生成、语法树分析)
- 典型案例:编译器的AST处理、报表生成器
- 在 Bean 初始化前后添加自定义逻辑
- Spring 中的体现:
- Spring 的
BeanPostProcessor访问和修改 Bean 实例
- Spring 的
- 实现细节:
BeanPostProcessor定义访问方法,应用于容器中的 Bean
- 详细介绍:
中介者(Mediator)
- 应用场景:
- 减少多个对象间的直接交互(如聊天室、GUI组件通信)
- 集中控制复杂交互(如MVC中的Controller)
- 减少控制器与视图间的直接交互
- 典型案例:Java的Mediator模式在Swing、Spring MVC的控制器
- Spring 中的体现:
- Spring MVC 的
DispatcherServlet作为中介,协调控制器、视图解析器等组件
- Spring MVC 的
- 实现细节:
DispatcherServlet集中处理请求分发和响应渲染
- 详细介绍:
备忘录(Memento)
- 应用场景:
- 保存和恢复对象状态(如游戏存档、事务回滚)
- 实现撤销功能(如文本编辑器的撤销)
- 典型案例:Java的Serializable接口、Hibernate的事务回滚
- 事务失败时恢复数据状态
- Spring 中的体现:
- Spring 的事务管理支持状态回滚(如数据库事务)
- 实现细节:
- 通过
TransactionManager保存和恢复事务状态
- 通过
- 详细介绍:
解释器(Interpreter)
- 应用场景:
- 定义特定语言的解释器(如正则表达式、SQL解析)
- 处理简单的脚本或规则(如规则引擎)
- 典型案例:Java的Pattern类(正则表达式)、SQL解析器
- 动态计算配置值或条件(如
@Value("#{systemProperties['user.dir']}"))
- Spring 中的体现:
- Spring Expression Language (SpEL) 用于解析表达式
- 实现细节:
- SpEL 解析器解释表达式,生成结果
- 详细介绍:
核心原则
-
以问题为导向:
- 设计模式是为了解决特定问题而存在的。不要为了使用模式而使用,而应根据项目中的具体问题选择合适的模式
- 例如:需要动态切换算法时使用策略模式,而不是因为“策略模式很流行”就强行应用
-
遵循设计原则(SOLID等):
- 设计模式通常基于面向对象设计原则(如单一职责原则、开闭原则、依赖倒置原则)。正确使用模式需要确保代码符合这些原则
- 例如:使用装饰者模式时,确保扩展功能而不修改原有类,符合开闭原则
-
保持简单(KISS原则):
- 优先选择简单的解决方案。设计模式增加了代码复杂性,只有在简单方案不足以解决问题时才引入
- 例如:小型项目中直接使用if-else可能比复杂的状态模式更直观
-
避免过度设计:
- 不要一开始就为所有可能的变化引入复杂模式。遵循YAGNI(You Aren’t Gonna Need It)原则,仅在需求明确时应用模式
- 例如:除非明确需要支持多种数据库,否则不要急于引入抽象工厂
-
关注可维护性和可读性:
- 设计模式应让代码更清晰、更易于维护,而不是增加理解难度。确保团队成员能理解所用模式
- 例如:使用单例模式时,清晰注释其全局访问点的作用
实用建议
-
学习经典模式:
- 熟练掌握GoF的23种设计模式,了解其定义、场景和实现
- 常用模式(如单例、工厂、观察者、策略)应重点掌握,因为它们在实际开发中出现频率高
-
结合框架和语言特性:
- 现代框架(如Spring、Django)内置了许多设计模式,了解框架如何实现模式可减少重复工作
- 示例:Spring的Bean容器大量使用单例模式,AOP使用代理模式
- 根据语言特性调整实现,如Java的枚举单例、Python的装饰器函数
-
优先使用成熟实现:
- 如果语言或框架提供了内置支持,优先使用,而不是自己重新实现
- 示例:Java的Iterator接口已实现迭代器模式,无需自己编写遍历逻辑
-
关注性能和复杂性:
- 评估模式对性能的影响,避免因模式引入过多开销
- 示例:双检锁单例需要正确使用volatile以保证性能和线程安全
-
避免模式滥用:
- 不要将设计模式当作银弹。某些问题可能通过简单重构或函数式编程解决
- 示例:小型项目中,简单的if-else可能比策略模式更易维护
-
考虑测试友好性:
- 设计模式可能增加测试难度(如单例的全局状态)。在实现时考虑如何支持单元测试
- 示例:为单例模式提供重置方法,便于测试
-
学习反模式(Anti-Patterns):
- 了解常见的设计模式误用案例,避免陷入陷阱
- 示例:滥用单例模式可能导致全局状态问题,难以调试和测试
注意事项
-
不要迷信设计模式:模式不是万能的,过度追求可能导致代码复杂化。简单问题用简单方案
-
避免“模式狂热”:新手开发者可能尝试在每个地方使用模式,导致代码冗余。例如,为简单配置类使用单例可能不必要
-
考虑团队水平:如果团队对设计模式不熟悉,优先选择易理解的模式,并提供培训或文档
-
平衡模式与框架:现代框架(如Spring、React)内置了模式,使用框架功能可能比手动实现更高效
-
定期重构:随着需求变化,某些模式可能不再适用。定期审查代码,必要时替换模式
-
警惕性能问题:某些模式(如代理模式或装饰者模式)可能增加调用层级,影响性能,需权衡
(对您有帮助 && 觉得我总结的还行) -> 受累点个免费的赞👍,谢谢