news 2026/7/16 15:17:50

第十二篇:设计模式——23种模式,本质上就是6种原则

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第十二篇:设计模式——23种模式,本质上就是6种原则

别再背23种模式的UML图了,你只需要知道"遇到什么问题,用什么套路解决"

写在前面

设计模式是面试里**最容易被"妖魔化"**的一块。很多人觉得设计模式很高深,要背23种模式的名字、结构图、适用场景……太痛苦了。

但设计模式的本质就一句话:针对特定问题的"经典解决方案"。

就像下棋有"开局套路"一样,写代码也有"套路"。这些套路是前人总结出来的,你不需要重新发明轮子,只需要知道"遇到什么问题,用什么套路"。

这篇文章的核心就一条线:6大原则 → 23种模式 → Spring里用了哪些。

一、六大设计原则(SOLID)

SOLID是设计模式的"指导思想"。所有设计模式,都是对这几条原则的"具体实现"。

1.1 单一职责原则(Single Responsibility)

一个类只干一件事。

如果一个类既是"订单管理器"又是"日志记录器"又是"邮件发送器",改订单逻辑可能影响邮件发送,改邮件配置又可能影响订单。拆成三个类,各干各的。

违反的例子:一个UserService里有createUser()sendEmail()logActivity()。应该把发邮件移到EmailService,日志移到LogService

1.2 开闭原则(Open-Closed)

对扩展开放,对修改关闭。

需求变化了,尽量加新代码而不是改旧代码

比如你有多种支付方式(微信、支付宝、银行卡),不要用if-else,而是定义Payment接口,每种支付方式实现这个接口。新增"Apple Pay"时,写一个新实现类就行,不需要改已有的支付逻辑。

1.3 里氏替换原则(Liskov Substitution)

子类应该能替换父类,程序行为不变。

如果父类是Bird,子类是Penguin,父类有fly()方法,企鹅不会飞,继承后就破坏了里氏替换。更好的设计:Bird作为抽象类,有move()方法,麻雀是Flyable,企鹅是Swimmable(用接口区分能力)。不要为了让代码复用而强行继承不合理的父类。

1.4 接口隔离原则(Interface Segregation)

接口要小而专,不要大而全。

一个"多功能接口"包含10个方法,实现类被迫实现一堆它用不上的方法。应该拆成多个小接口,按需实现。

比如Worker接口有work()eat()sleep(),如果只有Robot(机器人)不需要eat()sleep(),就该拆成WorkableEatable

1.5 依赖倒置原则(Dependency Inversion)

依赖抽象,不依赖具体。

上层模块(业务逻辑)不应该依赖下层模块(具体实现),两者都应该依赖抽象接口。

通俗说:你买了个台灯,台灯依赖的是"插座接口",而不是某个特定型号的插座。换房子了,新房子有插座,台灯就能用。如果台灯直接焊死在某个墙上插座上,搬家就没法用了。Spring的@Autowired注入接口而不是实现类,就是依赖倒置的体现——你随时可以切换实现类(比如从MySQL切换到Oracle),上层的Service代码不需要改。

1.6 迪米特法则(最少知道原则)

一个对象对其他对象了解越少越好。

A调B,B调C,A不要知道C的存在。A只管跟B打交道,B内部怎么调C是B自己的事。

如果A直接调C,A就绑死了C,C的改动会牵连A。

二、创建型模式(怎么创建对象)

2.1 单例模式(Singleton)

一个类只有一个实例。面试常问,重点是双重检查锁静态内部类两种写法。

懒汉式(双重检查锁)

publicclassSingleton{privatestaticvolatileSingletoninstance;// volatile禁止指令重排privateSingleton(){}publicstaticSingletongetInstance(){if(instance==null){synchronized(Singleton.class){if(instance==null){instance=newSingleton();}}}returninstance;}}

为什么用volatile?因为new Singleton()不是原子操作(分配内存→初始化→引用赋值),指令重排可能导致其他线程拿到一个未初始化完成的对象。volatile禁止重排。

静态内部类写法(推荐)

publicclassSingleton{privateSingleton(){}privatestaticclassHolder{privatestaticfinalSingletonINSTANCE=newSingleton();}publicstaticSingletongetInstance(){returnHolder.INSTANCE;// 类加载时初始化,线程安全}}

Spring里:所有Bean默认都是单例(@Scope("singleton"))。

2.2 工厂模式(Factory)

把创建对象的逻辑封装起来,而不是到处new

简单工厂:一个工厂类根据参数返回不同产品。

工厂方法:每个产品有自己的工厂类。

抽象工厂:创建"产品族"(比如MySQL的Connection、Statement、ResultSet是一族,Oracle的是一族)。

Spring里BeanFactoryApplicationContext都是工厂模式。你调getBean(),Spring帮你把对象创建出来。

2.3 建造者模式(Builder)

一步步构建复杂对象

Useruser=User.builder().name("张三").age(25).email("zhangsan@email.com").build();

好处:不用写一堆构造参数(特别是字段很多的时候),链式调用,可读性强。

Lombok@Builder注解底层就是建造者模式。

2.4 原型模式(Prototype)

通过拷贝现有对象来创建新对象,而不是new

Java里实现Cloneable接口,重写clone()方法。Spring里@Scope("prototype")就是原型模式,每次getBean()都返回一个新实例。

三、结构型模式(怎么组合类和对象)

3.1 代理模式(Proxy)

为其他对象提供一种代理,以控制对这个对象的访问。

静态代理:代理类手动编写,和目标类实现同一接口。

动态代理:运行时动态生成代理类。JDK动态代理(基于接口)和CGLIB(基于继承)。

Spring里:AOP底层就是动态代理。@Transactional@Cacheable这些注解,都是通过代理实现的。调userService.save(),实际调的是代理对象,代理先开启事务,再调原始对象的save(),再提交事务。

场景:远程代理(RPC)、虚拟代理(延迟加载)、保护代理(权限控制)。

3.2 适配器模式(Adapter)

把一个接口转换成另一个接口,让不兼容的类能一起工作。

生活比喻:你的手机是Type-C接口,充电头是USB-A口,中间加一个转换头(适配器)。

Spring里HandlerAdapter。Spring MVC里的Controller有多种实现方式(@Controller注解、实现Controller接口、实现HttpRequestHandler接口),Spring通过不同的HandlerAdapter把不同类型的Controller统一成handle()调用。

3.3 装饰器模式(Decorator)

不改变原有类,动态添加新功能

生活比喻:你买了一杯奶茶,可以加珍珠、加布丁、加椰果——每一种"配料"都是对奶茶的装饰,奶茶本身没变,但功能(口感)增强了。

Java里BufferedInputStream装饰FileInputStream,给文件读取增加了缓冲功能。BufferedReader装饰FileReader,增加了按行读取的方法。

3.4 外观模式(Facade)

给复杂的子系统提供一个统一的简化接口

生活比喻:你办护照不用自己去跑公安局、使馆、银行,全部交给旅行社(外观),旅行社帮你搞定一切。

Spring里JdbcTemplate就是外观模式,封装了获取Connection、创建Statement、处理ResultSet、关闭资源等一系列复杂操作,你只需要写SQL和映射逻辑。

四、行为型模式(怎么处理算法和职责分配)

4.1 模板方法模式(Template Method)

父类定义骨架(模板),子类实现具体细节。

生活比喻:做菜有个固定流程:洗菜→切菜→炒菜→装盘。但炒菜这个步骤,不同菜有不同的做法。父类定义cook()模板,子类重写heat()方法。

publicabstractclassAbstractService{publicfinalvoidexecute(){before();// 公共逻辑doBusiness();// 子类实现after();// 公共逻辑}protectedabstractvoiddoBusiness();}

Spring里JdbcTemplateexecute()定义了"获取连接→执行SQL→处理结果→释放连接"的骨架,具体SQL由你传入。RestTemplate也是模板方法模式的应用。

4.2 观察者模式(Observer)

对象状态变化时,通知所有依赖它的对象。

生活比喻:你订阅了某个UP主,UP主发新视频(状态变化),B站通知所有订阅者(观察者)。

Spring里ApplicationListenerApplicationEvent。发布一个OrderCreatedEvent,所有监听器(发邮件、扣库存、加积分)自动收到通知执行。这就是观察者模式。

4.3 策略模式(Strategy)

定义一系列算法,让它们可以互相替换

生活比喻:去机场,可以选地铁、打车、大巴。不同交通方式(策略)都能到机场,你可以根据需要选。

publicinterfacePaymentStrategy{voidpay(intamount);}@ServicepublicclassPaymentService{@AutowiredprivateMap<String,PaymentStrategy>strategyMap;publicvoidpay(Stringtype,intamount){strategyMap.get(type).pay(amount);}}

Spring里:依赖注入到Map<String, Handler>里,根据type选择不同的处理器。这在写业务代码时极其常见,是一种优雅的替代大规模if-else的方式。

4.4 责任链模式(Chain of Responsibility)

多个对象有机会处理同一个请求,形成一条链

生活比喻:请假审批。组长能批3天,经理能批7天,总监能批30天。你的请假申请从组长开始传,谁有权限谁批。

Spring里HandlerInterceptor(拦截器链)。一个请求经过多个拦截器的preHandle(),任何一个返回false就中断。Spring Security的过滤器链也是责任链。

五、Spring里用的设计模式(面试高频)

面试官常问:"Spring里用了哪些设计模式?"

设计模式Spring里的体现
单例模式所有Bean默认单例
工厂模式BeanFactoryApplicationContext
代理模式AOP动态代理
模板方法模式JdbcTemplateRestTemplate
观察者模式ApplicationEvent+ApplicationListener
适配器模式HandlerAdapter适配不同Controller
装饰器模式BeanWrapperHttpServletRequestWrapper
策略模式依赖注入到Map<String, Handler>,根据类型选择
责任链模式拦截器链、过滤器链

六、面试官真正想听什么

场景1:“你项目中用了哪些设计模式?”

不要说"我用了单例",这太初级了。说业务场景

加分回答:

“我们有个支付模块,支持微信、支付宝、银行卡等多种方式。我用了策略模式,定义PaymentStrategy接口,每种支付方式一个实现类,然后注入一个Map<String, PaymentStrategy>,根据前端传的payType动态选择策略类。这样新增一种支付方式时,只需要加一个实现类,不用改原有代码,符合开闭原则。”

“同时,支付成功后要通知多个模块(订单状态更新、积分增加、消息推送),我用了观察者模式(Spring的ApplicationEvent)。发送一个PaymentSuccessEvent,各个@EventListener异步处理,解耦了支付主流程和后续扩展逻辑。”

场景2:“策略模式和工厂模式有什么区别?”

加分回答:

工厂模式是为了创建对象策略模式是为了封装算法。简单说:工厂解决的是’怎么new出来’,策略解决的是’new出来后怎么用’。比如支付场景:工厂根据payType创建对应的支付策略对象,策略对象内部的pay()方法才是真正执行支付的算法。两者经常配合使用,但关注点完全不同。”

场景3:“知道哪些反模式?”

加分回答:

上帝对象(一个类做了所有事)违反单一职责;面条代码(大量if-else嵌套)难以维护;硬编码(配置写死在代码里)违背开闭原则。我们Code Review时会特别注意,看到超过3层的if-else就提醒用策略模式或状态机重构。”

小结

核心概念大白话面试考点
SOLID原则写代码的六大纪律设计模式的指导思想
单例模式一个类只能有一个实例双重检查锁+volatile
工厂模式封装创建逻辑BeanFactory
代理模式不碰原类,加增强功能AOP、动态代理
适配器模式把A接口转成B接口HandlerAdapter
模板方法父类定骨架,子类填细节JdbcTemplate
观察者模式状态变了通知所有人ApplicationEvent
策略模式多种算法互相替换消除if-else
责任链多个环节依次处理拦截器链

记住一句话:设计模式不是目的,是手段。代码要维护、要扩展、要别人看懂,用模式帮你做到"好维护、好扩展、好理解"。如果模式增加了复杂度,宁可不复用也不硬套。

下一篇我们讲系统设计——把前面学的所有技术串起来,解决真实业务问题。秒杀系统、短链系统、排行榜……怎么设计?怎么回答面试官追问的"挂了怎么办"?下期见。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/16 15:17:37

开关转换器补偿设计:原理、方案与工程实践

1. 开关转换器补偿设计的重要性与挑战在电源管理领域&#xff0c;开关转换器的稳定性直接决定了整个电源系统的可靠性。我曾在多个工业电源项目中遇到过这样的场景&#xff1a;精心设计的转换器在实验室测试时表现完美&#xff0c;一旦投入实际应用就出现振荡、效率骤降甚至器件…

作者头像 李华
网站建设 2026/7/16 15:17:32

JMeter 从零到一:Windows 环境下的完整配置与个性化设置指南

1. 环境准备&#xff1a;Java与JMeter的完美搭档 刚接触性能测试的朋友们&#xff0c;第一次打开JMeter官网可能会被满屏的英文和专业术语吓到。别担心&#xff0c;其实JMeter的安装就像组装乐高积木——只要按步骤来&#xff0c;谁都能轻松搞定。我们先来解决最基础的两个问题…

作者头像 李华
网站建设 2026/7/16 15:17:17

C++实现择式期权定价:蒙特卡洛模拟与量化金融工程实践

1. 项目概述&#xff1a;从“择式期权”到C量化实践在量化金融的世界里&#xff0c;期权定价模型是构建复杂交易策略的基石。我们经常接触欧式、美式期权&#xff0c;但有一类更具灵活性的奇异期权——择式期权&#xff0c;它在特定时点赋予持有者选择期权类型的权利&#xff0…

作者头像 李华
网站建设 2026/7/16 15:16:55

Comic Backup:构建个人数字漫画库的终极解决方案

Comic Backup&#xff1a;构建个人数字漫画库的终极解决方案 【免费下载链接】comic-backup Back up your comics as CBZ. 项目地址: https://gitcode.com/gh_mirrors/co/comic-backup 在数字时代&#xff0c;漫画爱好者面临着版权平台内容访问限制和数字资产长期保存的…

作者头像 李华
网站建设 2026/7/16 15:15:25

若依框架在IDEA中的一站式部署与核心模块解析

1. 若依框架简介与IDEA环境准备 若依&#xff08;RuoYi&#xff09;是一个基于Spring Boot的Java EE企业级快速开发平台&#xff0c;它整合了Apache Shiro、MyBatis、Thymeleaf等主流技术栈。我第一次接触这个框架是在2018年&#xff0c;当时就被它完善的后台管理功能所吸引。经…

作者头像 李华