设计模式常见“学习”题总结
本文总结了 Java 领域常见的 50 道设计模式面试题,涵盖设计原则、创建型模式、结构型模式、行为型模式以及在 Spring/JDK 源码中的应用。
一、设计原则 (Design Principles)
1. 什么是 SOLID 原则?
SOLID 是面向对象设计的五个基本原则的首字母缩写:
- S (Single Responsibility Principle):单一职责原则。一个类应该只有一个引起它变化的原因。
- O (Open/Closed Principle):开闭原则。对扩展开放,对修改关闭。
- L (Liskov Substitution Principle):里氏替换原则。子类必须能够替换掉它们的父类。
- I (Interface Segregation Principle):接口隔离原则。客户端不应该依赖它不需要的接口。
- D (Dependency Inversion Principle):依赖倒置原则。高层模块不应该依赖低层模块,二者都应该依赖其抽象。
📝 通俗解释
SOLID 原则:好代码的五大护法。
- S:专一。就像瑞士军刀虽然好用,但不如专业的螺丝刀好使。一个类只做一件事。
- O:加装。想给车加个尾翼直接装就行(扩展),别把车尾锯了重焊(修改)。
- L:替身。父亲能干的活,儿子必须也能干,不能儿子一干就报错。
- I:按需分配。我要买插座,你别给我硬塞个发电机。接口要小而精。
- D:靠谱。老板(高层)只管发号施令(抽象接口),不管员工(底层)怎么干活。
2. 什么是单一职责原则 (SRP)?
单一职责原则规定一个类应该只有一个发生变化的原因。如果一个类承担了过多的职责,那么这些职责就会耦合在一起,一个职责的变化可能会削弱或抑制这个类完成其他职责的能力。
📝 通俗解释
单一职责:各司其职。
- 就像公司的职位,会计只管算账,销售只管卖货。如果让会计既算账又修电脑,那修电脑的时候账就没人算了。
3. 什么是开闭原则 (OCP)?
开闭原则要求软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。这意味着当需要添加新功能时,应该通过扩展现有代码来实现,而不是修改已有的代码。
📝 通俗解释
开闭原则:穿衣服。
- 天冷了,你应该加一件外套(扩展),而不是把皮肤撕开塞点棉花进去(修改)。
4. 什么是里氏替换原则 (LSP)?
里氏替换原则规定,子类对象必须能够替换掉所有父类对象,而程序的逻辑行为不会发生改变。这要求子类尽量不要重写父类的非抽象方法,如果要重写,也要保证不破坏父类的原有逻辑。
📝 通俗解释
里氏替换:父慈子孝。
- 如果你是你爸的儿子,那你爸能喝的酒,你也得能喝(至少不能喝一口就倒)。
- 任何能用你爸的地方,换成你也得行,不能掉链子。
5. 什么是接口隔离原则 (ISP)?
接口隔离原则要求尽量将臃肿庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户感兴趣的方法。
📝 通俗解释
接口隔离:自助餐。
- 不要把所有菜都盛到一个盘子里给客人。
- 应该分盘装,客人想吃什么拿什么,别强迫客人吃不喜欢的菜。
6. 什么是依赖倒置原则 (DIP)?
依赖倒置原则要求高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。核心思想是面向接口编程。
📝 通俗解释
依赖倒置:组装电脑。
- 主板(高层)不应该直接焊死在 CPU(底层)上。
- 主板和 CPU 都应该依赖标准的插槽接口(抽象)。只要接口对,换个 CPU 也能用。
7. 什么是迪米特法则 (LoD)?
迪米特法则又叫最少知识原则,一个对象应该对其他对象保持最少的了解。只与你的直接朋友交谈,不跟“陌生人”说话。
📝 通俗解释
迪米特法则:中介。
- 你想租房,直接找中介(朋友),不用自己去找房东、去办手续。
- 你只管跟中介说话,剩下的事中介去办,别管太多闲事。
8. 什么是合成复用原则 (CRP)?
尽量使用对象组合/聚合,而不是继承关系达到软件复用的目的。继承是强耦合,组合是弱耦合。
📝 通俗解释
合成复用:搭积木。
- 想要个带轮子的箱子。
- 继承:造一个“轮子箱”类。
- 组合:找个箱子,找几个轮子,拼在一起。显然拼在一起更灵活。
二、创建型模式 (Creational Patterns)
9. 什么是单例模式 (Singleton)?有哪些实现方式?
单例模式保证一个类只有一个实例,并提供一个全局访问点。
常见实现方式:
- 饿汉式:类加载时创建,线程安全,但可能浪费资源。
- 懒汉式:用时创建,需要双重检查锁 (DCL) 保证线程安全。
- 静态内部类:利用类加载机制保证线程安全且延迟加载。
- 枚举:最推荐,天然线程安全,防止反射和序列化破坏。
📝 通俗解释
单例模式:太阳。
- 天上只能有一个太阳。
- 不管谁看,看到的都是同一个太阳。
10. 为什么双重检查锁 (DCL) 单例需要 volatile 关键字?
volatile 关键字主要用于防止指令重排序。在 instance = new Singleton() 过程中,JVM 会进行三步操作:
- 分配内存空间。
- 初始化对象。
- 将 instance 指向分配的内存。
如果不加volatile,步骤 2 和 3 可能重排序。线程 A 执行了 1 和 3(此时 instance 非空但未初始化),线程 B 进来判断 instance != null,直接拿去用,就会报错。
📝 通俗解释
volatile:禁止插队。
- 造房子有三步:买地、盖房、挂门牌。
- 如果不加限制,工人可能先挂门牌再盖房。
- 此时有人看到门牌(instance != null),以为房子好了,冲进去结果掉坑里了(对象未初始化)。
11. 枚举单例为什么是最好的单例实现?
枚举单例由 JVM 保证线程安全,且 JVM 保证枚举对象在序列化和反序列化过程中不会创建新对象,同时也能防止反射攻击(Constructor 中会检查是否为枚举)。
📝 通俗解释
枚举单例:官方认证。
- 别的单例是自己写的逻辑,可能会有漏洞(反射、序列化)。
- 枚举是 Java 语言层面提供的特性,JVM 亲自保驾护航,谁也别想造出第二个。
12. 什么是工厂模式 (Factory Pattern)?
工厂模式提供了一种创建对象的最佳方式。我们在创建对象时不会对客户端暴露创建逻辑,而是通过使用一个共同的接口来指向新创建的对象。
主要分为:
- 简单工厂:一个工厂类根据传入参数决定创建哪种产品。
- 工厂方法:定义一个创建对象的接口,让子类决定实例化哪一个类。
- 抽象工厂:创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
📝 通俗解释
工厂模式:富士康。
- 你要手机,不用自己买零件拼。
- 直接找富士康(工厂),说要“iPhone 15”,它就直接给你一台做好的手机。
13. 简单工厂、工厂方法和抽象工厂的区别?
- 简单工厂:一个工厂生产所有产品(上帝工厂),违背开闭原则。
- 工厂方法:每个产品对应一个工厂(一品一厂),扩展新产品只需加新工厂。
- 抽象工厂:每个工厂生产一个产品族(如海尔工厂生产海尔冰箱、海尔空调)。
📝 通俗解释
区别:
- 简单工厂:路边摊,既卖煎饼又卖烤冷面。
- 工厂方法:专卖店,煎饼店只卖煎饼,烤冷面店只卖烤冷面。
- 抽象工厂:电器城,海尔区卖海尔全家桶,格力区卖格力全家桶。
14. 什么是建造者模式 (Builder Pattern)?
建造者模式使用多个简单的对象一步一步构建成一个复杂的对象。它允许用户只通过指定复杂对象的类型和内容就可以构建它们,而不需要知道内部的具体构建细节。
📝 通俗解释
建造者:Subway 选配。
- 买个三明治(复杂对象)。
- 你说:面包要全麦的,肉要金枪鱼,菜要生菜,酱要蛋黄酱。
- 店员(Builder)一步步给你组装好。
15. 建造者模式和工厂模式的区别?
- 工厂模式:关注创建单个产品,一步到位。
- 建造者模式:关注创建复杂产品,分步构建,精细化控制。
📝 通俗解释
区别:
- 工厂:买整车。去 4S 店提一辆标准版汽车。
- 建造者:改装车。去改装店,选轮毂、选尾翼、选发动机,最后组装出来。
16. 什么是原型模式 (Prototype Pattern)?
原型模式用于创建重复的对象,同时又能保证性能。它通过克隆(Clone)一个现有对象来生成新对象,而不是通过 new 关键字。
📝 通俗解释
原型模式:复印机。
- 你有一份写好的作业(原型)。
- 想再要一份,不用重新写一遍,直接复印(Clone)一份就行。
17. 深拷贝和浅拷贝的区别?
- 浅拷贝:只复制对象本身和基本数据类型,引用类型只复制引用(地址),新旧对象共享同一个引用对象。
- 深拷贝:复制对象本身和所有引用指向的对象,新旧对象完全独立。
📝 通俗解释
深浅拷贝:文件快捷方式。
- 浅拷贝:复制快捷方式。原来的文件删了,快捷方式也就废了。
- 深拷贝:复制整个文件。原来的文件删了,新的文件还在。
三、结构型模式 (Structural Patterns)
18. 什么是代理模式 (Proxy Pattern)?
代理模式为其他对象提供一种代理以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用。
📝 通俗解释
代理模式:经纪人。
- 你想找明星演出,不能直接找明星。
- 要先找经纪人(代理),经纪人谈好价钱、安排好时间,最后才让明星上台。
19. 静态代理和动态代理的区别?
- 静态代理:代理类在编译期就生成了,一个代理类通常只能代理一个接口。
- 动态代理:代理类在运行期通过反射机制动态生成,可以代理各种接口。
📝 通俗解释
区别:
- 静态代理:私人助理。只服务你一个人,随叫随到。
- 动态代理:共享秘书。谁需要服务就临时分配给谁,通用性强。
20. JDK 动态代理和 CGLIB 动态代理的区别?
- JDK 动态代理:基于接口实现。只能代理实现了接口的类。利用
InvocationHandler和Proxy类。 - CGLIB 动态代理:基于继承实现。可以代理没有接口的类。利用 ASM 操作字节码生成子类。
📝 通俗解释
区别:
- JDK:持证上岗。必须有接口(证书)才能代理。
- CGLIB:认干爹。直接继承目标类,生成一个子类来代理(final 类不能认干爹,所以不能代理)。
21. 什么是适配器模式 (Adapter Pattern)?
适配器模式作为两个不兼容的接口之间的桥梁。它结合了两个独立接口的功能。
📝 通俗解释
适配器:转接头。
- 你的电脑只有 Type-C 口,U 盘是 USB 口。
- 插不进去怎么办?买个转接头(适配器),两边一连就通了。
22. 适配器模式和代理模式的区别?
- 适配器:改变接口,让不兼容的接口能一起工作。
- 代理:不改变接口,主要是控制访问或增强功能。
📝 通俗解释
区别:
- 适配器:翻译官。把中文翻译成英文,让两人能沟通。
- 代理:门卫。不改变你说的话,但是进门前检查证件,或者进门后给你倒杯水。
23. 什么是装饰器模式 (Decorator Pattern)?
装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构。它是继承的一种替代方案,比继承更灵活。
📝 通俗解释
装饰器:奶茶加料。
- 一杯基础奶茶(原对象)。
- 加珍珠(装饰器 1),加椰果(装饰器 2),加布丁(装饰器 3)。
- 最后还是一杯奶茶,但是口感丰富了。
24. 装饰器模式和代理模式的区别?
- 装饰器:关注于给对象增加功能。
- 代理:关注于控制对对象的访问。
📝 通俗解释
区别:
- 装饰器:化妆。为了更漂亮(增强功能)。
- 代理:替身。为了保护真身(控制访问)。
25. 什么是外观模式 (Facade Pattern)?
外观模式为子系统中的一组接口提供一个一致的界面,定义了一个高层接口,这个接口使得这一子系统更加容易使用。
📝 通俗解释
外观模式:一键启动。
- 电脑开机涉及 CPU、内存、硬盘等一系列复杂操作。
- 你只需要按一个电源键(外观接口),剩下的复杂操作电脑自己搞定。
26. 什么是桥接模式 (Bridge Pattern)?
桥接模式用于将抽象部分与实现部分分离,使它们都可以独立地变化。
📝 通俗解释
桥接模式:颜色和形状。
- 想要红色的圆、红色的方、蓝色的圆、蓝色的方。
- 不要写 4 个类。
- 把“颜色”和“形状”分开,然后组合(桥接)在一起。
27. 什么是组合模式 (Composite Pattern)?
组合模式将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
📝 通俗解释
组合模式:文件夹。
- 文件夹里可以有文件,也可以有文件夹。
- 不管是文件还是文件夹,你都可以进行“复制”、“删除”操作,不用区别对待。
28. 什么是享元模式 (Flyweight Pattern)?
享元模式主要用于减少创建对象的数量,以减少内存占用和提高性能。它通过共享技术有效地支持大量细粒度的对象。
📝 通俗解释
享元模式:共享单车。
- 不需要每个人都买一辆自行车。
- 大家共用路边的车,谁用谁扫码。省钱省地。
四、行为型模式 (Behavioral Patterns)
29. 什么是策略模式 (Strategy Pattern)?
策略模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。
📝 通俗解释
策略模式:锦囊妙计。
- 遇到敌人(问题),打开锦囊(策略)。
- 锦囊 A 是“火攻”,锦囊 B 是“水淹”,锦囊 C 是“逃跑”。
- 根据情况灵活切换锦囊。
30. 策略模式和工厂模式的区别?
- 工厂模式:关注对象的创建。
- 策略模式:关注对象的行为(算法)。
📝 通俗解释
区别:
- 工厂:给你造出一辆车。
- 策略:决定这辆车怎么开(走高速、走国道、走土路)。
31. 什么是模板方法模式 (Template Method Pattern)?
模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
📝 通俗解释
模板方法:试卷。
- 老师发下来的试卷(模板),题目都是一样的(骨架)。
- 每个学生的答案不一样(子类实现)。
32. 什么是观察者模式 (Observer Pattern)?
观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
📝 通俗解释
观察者:订阅公众号。
- 你订阅了“JavaGuide”(观察者)。
- JavaGuide 发文章了(被观察者状态改变)。
- 你手机立马收到推送(通知)。
33. 什么是责任链模式 (Chain of Responsibility Pattern)?
责任链模式为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。
📝 通俗解释
责任链:击鼓传花 / 审批流。
- 请假 3 天,组长批。
- 请假 7 天,组长批不了,递给经理批。
- 请假 30 天,经理批不了,递给老板批。
- 一个请求沿着链条传递,直到有人处理。
34. 什么是迭代器模式 (Iterator Pattern)?
迭代器模式提供一种方法顺序访问一个聚合对象中各个元素,而又无须暴露该对象的内部表示。
📝 通俗解释
迭代器:遥控器。
- 你不关心电视机里面怎么布线的。
- 你只管按“下一个频道”(next),就能看到下一个台。
35. 什么是命令模式 (Command Pattern)?
命令模式将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化。
📝 通俗解释
命令模式:点菜。
- 你想吃宫保鸡丁,不用直接去厨房指挥厨师炒菜。
- 你写在单子上(Command),服务员把单子拿给厨师。
- 单子可以撤回、可以排队、可以记录。
36. 什么是状态模式 (State Pattern)?
状态模式允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
📝 通俗解释
状态模式:变身。
- 绿巨人平时是班纳博士(正常状态),温文尔雅。
- 生气了变成绿巨人(愤怒状态),力大无穷。
- 状态变了,行为完全变了。
37. 什么是中介者模式 (Mediator Pattern)?
中介者模式用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
📝 通俗解释
中介者:群聊。
- 没有群聊前,你要通知 10 个人,得私聊 10 次(网状结构,乱)。
- 有了群聊(中介者),你发一条消息,大家都收到了(星型结构,清晰)。
38. 什么是备忘录模式 (Memento Pattern)?
备忘录模式在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
📝 通俗解释
备忘录:游戏存档。
- 打 Boss 前存个档(保存状态)。
- 打输了读档(恢复状态),又是一条好汉。
39. 什么是访问者模式 (Visitor Pattern)?
访问者模式主要将数据结构与数据操作分离。
📝 通俗解释
访问者:药房抓药。
- 药材(数据结构)就在柜子里不动。
- 医生 A 开感冒药(访问者 A),抓这几种药。
- 医生 B 开补药(访问者 B),抓那几种药。
- 药材不变,抓药的方式在变。
40. 什么是解释器模式 (Interpreter Pattern)?
解释器模式提供了评估语言的语法或表达式的方式。
📝 通俗解释
解释器:翻译机。
- 只有机器人听得懂 "1+1"。
- 解释器把 "1+1" 翻译成机器人能懂的指令。
- 正则表达式就是一种典型的解释器。
五、框架源码中的设计模式 (Framework Patterns)
41. Spring 中用到了哪些设计模式?
- 工厂模式:BeanFactory、ApplicationContext。
- 代理模式:AOP(JDK 动态代理、CGLIB)。
- 单例模式:Bean 默认为单例。
- 模板方法:JdbcTemplate、RestTemplate。
- 适配器模式:HandlerAdapter(MVC 中适配不同的 Controller)。
- 装饰器模式:BeanWrapper。
- 观察者模式:ApplicationEvent、Listener。
- 策略模式:Resource 接口的不同实现。
📝 通俗解释
Spring:设计模式集大成者。
- Spring 框架本身就是一本活着的设计模式教科书。
42. JDK 中用到了哪些设计模式?
- 单例:Runtime.getRuntime()。
- 工厂:Integer.valueOf()。
- 装饰器:java.io 包(BufferedInputStream 装饰 FileInputStream)。
- 适配器:Arrays.asList()(数组转 List)。
- 迭代器:Iterator。
- 观察者:Swing 中的 Listener。
📝 通俗解释
JDK:处处留心皆学问。
- Java 语言库里到处都是设计模式的影子,特别是 IO 流,包了一层又一层,典型的装饰器。
43. Spring Bean 是单例的吗?线程安全吗?
Spring Bean 默认是单例的(Singleton)。但是 Spring 并不保证 Bean 的线程安全。如果 Bean 是无状态的(如 Service、Dao),通常是线程安全的。如果 Bean 有状态(定义了成员变量),则需要开发者自己保证线程安全(如使用 ThreadLocal 或改为 Prototype 作用域)。
📝 通俗解释
Spring Bean:共享单车。
- 大家都骑同一辆车(单例)。
- 如果你在车篮里放了东西(有状态),别人骑的时候可能会拿走(线程不安全)。
44. Spring AOP 是怎么实现代理的?
Spring AOP 自动判断:
- 如果目标对象实现了接口,默认使用 JDK 动态代理。
- 如果目标对象没有实现接口,使用 CGLIB 代理。
- 也可以通过配置强制使用 CGLIB。
📝 通俗解释
AOP 代理:看人下菜碟。
- 有证(接口)的走正规渠道(JDK)。
- 没证的走后门(CGLIB 继承)。
45. Mybatis 中用到了哪些设计模式?
- 工厂模式:SqlSessionFactory。
- 建造者模式:SqlSessionFactoryBuilder。
- 代理模式:MapperProxy(接口方法的动态代理)。
- 模板方法:BaseExecutor。
📝 通俗解释
Mybatis:数据库管家。
- 也是用了一堆模式来管理数据库连接和 SQL 执行。
46. IO 流为什么要用装饰器模式?
为了避免类爆炸。如果用继承,FileInputStream 要加缓冲功能得写个子类,加加密功能得写个子类,既要缓冲又要加密还得写个子类。组合方式就灵活多了,想加什么功能套一层就行。
📝 通俗解释
IO 装饰器:套娃。
- 核心是“读文件”。
- 套一层“缓冲娃”,读得快。
- 再套一层“解密娃”,能读加密文件。
- 想要什么功能就套什么娃,不用造一堆新娃。
47. 线程池中用到了什么设计模式?
- 享元模式:线程池中的线程被重复利用,避免了频繁创建和销毁。
- 工厂模式:ThreadFactory 用于创建线程。
- 观察者模式(部分实现):任务完成后可能有回调。
📝 通俗解释
线程池:劳务市场。
- 这里的工人(线程)干完活不回家,蹲在市场等下一个活(享元)。
48. Java 字符串常量池用到了什么模式?
享元模式。字符串常量池缓存了字符串字面量,如果池中有相同的字符串,直接返回引用,不再创建新对象。
📝 通俗解释
字符串常量池:共享文档。
- 你写“Hello”,我写“Hello”。
- 内存里只有一份“Hello”,我们俩都指向它。
49. Spring MVC 的 DispatcherServlet 用到了什么模式?
- 前端控制器模式 (Front Controller):DispatcherServlet 作为统一入口,分发请求。
- 策略模式:HandlerMapping 解析请求找到 Handler。
- 适配器模式:HandlerAdapter 执行 Handler。
📝 通俗解释
DispatcherServlet:前台。
- 所有客人都先到前台。
- 前台看你去哪个部门(策略),找哪个接待员(适配器),然后领你过去。
50. 为什么说枚举单例能防反序列化破坏?
Java 规范规定,在序列化枚举时,只保存枚举常量的名称。反序列化时,通过名称查找枚举常量。因此,反序列化后的对象还是原来的那个对象,不会创建新的。
📝 通俗解释
枚举防破解:实名制。
- 枚举对象在序列化的时候,只存了个名字。
- 回来的时候,凭名字去查户口本,查到的还是原来那个人。
