设计模式零基础入门:23种模式分类、代码实现与面试实战
2026/9/24 22:47:57 网站建设 项目流程

设计模式这四个字,劝退过不少人。我刚接触那会儿,翻开书看到23个名字,第一反应是:这玩意儿是给人背的吗?后来踩过几次坑、啃了几轮源码、面试被问过几轮,才慢慢摸到门道——设计模式不是靠背的,是靠“场景”驱动的。它解决的根本问题就一个:当需求发生变化时,你的代码改起来是像换灯泡一样轻松,还是像拆承重墙一样痛苦。

这篇内容是我自己从零基础到能实际用起来的完整思路。不讲玄学,不堆概念,用大白话把23种设计模式的分类、记忆口诀、核心代码、UML类图、面试题思路全部串起来。不管你是要应付期末考试、软考速记,还是写设计模式大作业、准备Java面试,这篇文章都够用。收藏好,别吃灰。

1. 设计模式到底在解决什么问题

1.1 从一段让人头大的代码说起

先看一段很多新手都写过的代码。假设你做一个商城支付功能,第一种写法是这样的:

public void pay(String type, double amount) { if ("wechat".equals(type)) { System.out.println("使用微信支付:" + amount); } else if ("alipay".equals(type)) { System.out.println("使用支付宝支付:" + amount); } else if ("card".equals(type)) { System.out.println("使用银行卡支付:" + amount); } }

这段代码在功能上没问题,但问题在“需求变更”的时候。今天加一个云闪付,你要改这个方法;明天加一个余额支付,你还要改这个方法。改来改去,要么出现一堆else if,要么一不留神把原有逻辑改坏了。这就是典型的违背开闭原则:对扩展不开放,对修改不关闭。

设计模式解决的就是这类问题。它不负责提升代码的性能,不负责减少代码的行数,它负责的是应对变化——让代码在需求一波波涌来的时候,仍然好读、好改、好维护。你想想,为什么有人写三年代码还是天天在改bug?很多时候不是逻辑难度大,而是代码结构烂,牵一发而动全身。

1.2 面向对象基本功和设计模式的关系

如果把写代码比作盖房子,那封装、继承、多态就是砖头、水泥和钢筋,而设计模式就是施工图纸。只有砖头你只能盖个平房,照着图纸施工你才能盖出框架清晰的大楼。当然,图纸也不是越多越好,一个茅草屋完全没必要上全套摩天大楼图纸。

所以零基础学设计模式之前,你得先确认自己有这几个基本功:知道classinterface的区别,知道继承是什么,知道什么是向上转型,知道多态是怎么实现的。如果不熟,建议先花两小时复习一下。不是说设计模式非要很高基础才能学,而是“类是模板,接口是契约,多态是运行时才确定具体干活的人”这几个概念,后面所有模式都用得上。

1.3 六大设计原则,是理解所有模式的总钥匙

23种设计模式太多,但它们的“指导思想”其实只有六条,也就是常说的SOLID原则,再加上一个迪米特法则。我把它们翻译成人话:

  • 单一职责原则(SRP):一个类只干一件事。类看多了容易糊涂,一个类管天管地,最后谁都不敢动它。
  • 开闭原则(OCP):对扩展开放,对修改关闭。加新功能不靠改旧代码,靠加新代码。
  • 里氏替换原则(LSP):子类要能替换父类,还不能破坏程序正确性。说白了就是别乱继承。
  • 依赖倒置原则(DIP):面向接口编程,依赖抽象,不要依赖具体实现。
  • 接口隔离原则(ISP):接口不要太大太全,要小而专。胖接口是灾难。
  • 迪米特法则(LOD):不和陌生人说话,一个对象应尽量少了解别的对象内部细节。

你看,拿到任意一个设计模式,都可以用这几条原则去解释“为什么要这么设计”。比如策略模式,就是把每个算法封装成独立类,互相可以替换——这是开闭原则和依赖倒置原则的典型应用;观察者模式里,被观察者只管通知,观察者各自处理自己的逻辑——这是单一职责的体现。

原则是“道”,模式是“术”。道领会了,术就活了。

1.4 学设计模式的实际收益,别只盯着面试

对大部分人来说,学设计模式最直接的两个动力是考试和面试。但我自己工作几年后的体会是,设计模式更大的价值在于沟通。当你跟队友说“这个通知模块用观察者来做”,对方立刻就知道你的意图和代码结构;当你在代码里定义了Strategy接口,新人进来扫一眼类名,大概就知道该往哪里扩展。

另一个价值是帮你读懂源码。Spring、MyBatis、Netty这些框架内部大量使用设计模式。如果你不懂模板方法模式,看JdbcTemplate的源码会一头雾水;不懂责任链模式,看Netty的 Pipeline 会直接劝退。学设计模式不是终点,它是你打开源码世界的一把钥匙。

2. 23种设计模式怎么分类、怎么记

2.1 三种分类:创建型、结构型、行为型

23种设计模式最早出自GoF(四人组)的《设计模式:可复用面向对象软件的基础》,它们分成三大类:

  • 创建型(5种):解决的是“怎么把对象创建出来更合理”的问题,把创建过程从业务代码中解耦出来。
  • 结构型(7种):解决的是“类和对象怎么组合成更大的结构”的问题,让组合方式更灵活。
  • 行为型(11种):解决的是“对象之间怎么协作、职责怎么分配”的问题。

打个比方,创建型关心“零件怎么造”,结构型关心“零件怎么拼”,行为型关心“拼好的机器怎么协同工作”。

2.2 一张表记住全部23种模式

下面把23种模式全部列出来,每种给了极简一句话定位。

类别模式名一句话定位
创建型单例模式全局只允许一个实例
创建型工厂方法模式定义一个创建对象的接口,由子类决定实例化哪个类
创建型抽象工厂模式创建一组相关或相互依赖的对象,而不指定具体类
创建型建造者模式分步骤构建复杂对象,构建过程与表示分离
创建型原型模式通过复制现有对象来创建新对象
结构型适配器模式让接口不兼容的类能一起工作
结构型桥接模式把抽象部分和实现部分分离,使它们可以独立变化
结构型组合模式把对象组织成树形结构,对单个对象和组合对象一致对待
结构型装饰器模式动态给对象增加功能,替代继承
结构型外观模式给复杂子系统提供一个统一的门面接口
结构型享元模式共享对象,减少大量细粒度对象的创建
结构型代理模式为对象提供一个替身,控制对它的访问
行为型策略模式定义一组算法,使其可以互相替换
行为型模板方法模式父类定义算法骨架,子类实现可变步骤
行为型观察者模式对象一对多通知,状态变化自动广播
行为型中介者模式用一个中介对象封装对象之间的交互
行为型责任链模式请求沿着处理链传递,直到有人处理
行为型解释器模式定义语言的文法并解释句子
行为型命令模式把请求封装成对象,支持撤销、队列等操作
行为型状态模式对象行为随内部状态改变而改变
行为型迭代器模式顺序访问集合元素而不暴露内部结构
行为型访问者模式在不改变元素类的前提下,增加对其的新操作
行为型备忘录模式捕获并保存对象状态,便于恢复

配上记忆口诀,方便你快速过一遍:

  • 创建型五兄弟:“工抽单建原”——工厂方法、抽象工厂、单例、建造者、原型。
  • 结构型七君子:“适桥组装外享代”——适配器、桥接、组合、装饰器、外观、享元、代理。
  • 行为型十一罗汉:“策模观中责解命状迭访备”——策略、模板方法、观察者、中介者、责任链、解释器、命令、状态、迭代器、访问者、备忘录。

口诀读起来有点拗口,但你先把它读顺,再回头对应表格,脑中就有了一张“地图”。考试前默写一遍分类,能帮你稳稳保住基础分。

2.3 每个模式适合什么场景,先建个场景脑图

记模式不能只记名字,要把“模式名”和“场景”绑在一起。我自己的方法是给每个模式贴上场景标签:

  • 只要你想让某个类全局只有一个实例,想到单例。
  • 只要你在创建对象时不想让业务代码直接 new,想到工厂系列。
  • 只要对象构造参数太多太复杂,想到建造者。
  • 只要有一组可替换的算法或行为,想到策略。
  • 只要一个对象变化需要通知一堆依赖者,想到观察者。
  • 只要一段流程骨架固定、细节多变,想到模板方法。
  • 只要想给对象动态加功能,或想控制访问,分别在装饰器和代理里选。
  • 只要有层层审批、层层过滤的逻辑,想到责任链。
  • 只要对象有很多状态、状态切换带来行为变化,想到状态模式。

这层脑图建立起来后,你看任何业务需求,都会自然浮现对应的模式。后文第5章的速查表我会再给一份更详细的对照。

3. 核心模式逐个拆开揉碎:代码+原理+坑

23种模式全部展开写,篇幅会非常长,也容易让零基础的同学消化不良。我挑七个最高频、面试最爱问、项目里最常用的模式,把原理和代码讲透。其余模式理解了场景定位,用到时再查书完全来得及。

3.1 单例模式:面试常客,坑也是最多的

单例模式,保证一个类在JVM里只有一个实例。听起来简单,写起来处处是坑。最常见的写法是双重检查锁

public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }

这里最容易被忽略的是volatile关键字。为什么需要它?因为new Singleton()不是原子操作,它分三步:分配内存、初始化对象、把引用指向内存。如果没有volatile,CPU和编译器可能重排指令,导致线程A先让引用指向了内存(但对象还没初始化完),线程B此时判断instance != null,拿到的就是一个还没构造好的半成品对象。这不是理论问题,是真实会踩的并发坑。

除了双重检查锁,还有三种常见写法:饿汉式(类加载时就初始化,简单安全但可能造成内存浪费)、静态内部类(利用类加载机制实现懒加载且线程安全)、枚举单例(天然防反射、防序列化,是《Effective Java》作者强烈推荐的写法)。

面试时还有两个进阶坑大概率被追问:反射能破坏单例吗?能。反射可以绕过私有构造器强制调用构造方法,解决办法是在构造器里加判断,如果实例已存在就抛异常。序列化能破坏单例吗?能。序列化后反序列化会生成新对象,解决办法是实现readResolve()方法,让它直接返回已有实例。

单例模式在真实框架里到处都是,最典型的就是Spring容器里的Bean。Spring默认Bean就是单例的,当然Spring容器自己还会再处理一层,但它解决的核心诉求是一致的:一个组件全局只保留一份,避免频繁创建和状态不一致。

3.2 工厂方法 vs 抽象工厂:把“创建对象”抽出来

工厂系列的核心思想是:不直接 new,把创建对象的逻辑放到专门的类里。为什么?因为直接 new 会把“依赖”焊死在业务代码里,一旦替换实现类,就得改业务代码。

先从最简单的简单工厂说起,它其实不在23种模式里,但理解它有助入门:

public class PayFactory { public static Pay create(String type) { if ("wechat".equals(type)) { return new WechatPay(); } else if ("alipay".equals(type)) { return new AliPay(); } throw new IllegalArgumentException("未知支付方式"); } }

简单工厂把if-else集中挪到了一个地方,比散落在业务代码里强,但它违背开闭原则——加一个支付方式,还是要改PayFactoryif-else

工厂方法模式进一步解决这个问题。它把工厂抽象成接口,每个产品对应一个工厂:

public interface PayFactory { Pay create(); } public class WechatPayFactory implements PayFactory { @Override public Pay create() { return new WechatPay(); } } public class AliPayFactory implements PayFactory { @Override public Pay create() { return new AliPay(); } }

这样新增支付方式时,你只需要新增XxxPayXxxPayFactory,完全不改老代码。这就是开闭原则的落地。

抽象工厂模式则更进一步,它针对“一族产品”的创建。举个经典例子:一家手机组装厂,既生产手机,又生产充电器、数据线。如果是苹果代工厂,生产的是苹果手机、苹果充电器、苹果数据线;如果是华为代工厂,生产的是华为手机、华为充电器、华为数据线。这种“一整套相关产品”的创建,用抽象工厂:

public interface Factory { Phone createPhone(); Charger createCharger(); } public class AppleFactory implements Factory { @Override public Phone createPhone() { return new IPhone(); } @Override public Charger createCharger() { return new AppleCharger(); } }

一句话区分:工厂方法是用一个工厂造一种产品,抽象工厂是用一个工厂造一整套产品。日常开发中工厂的变体很多,比如Spring的BeanFactory、日志框架的LoggerFactory,本质都是在做“不直接new,把创建权交给工厂”这件事。

3.3 策略模式:消灭业务if-else的利器

策略模式是我项目里用得最多的模式。还是回到开头的支付场景,用策略模式重构:

public interface PayStrategy { void pay(double amount); } public class WechatPay implements PayStrategy { @Override public void pay(double amount) { System.out.println("使用微信支付:" + amount); } } public class AliPay implements PayStrategy { @Override public void pay(double amount) { System.out.println("使用支付宝支付:" + amount); } } public class PayContext { private PayStrategy strategy; public void setStrategy(PayStrategy strategy) { this.strategy = strategy; } public void executePay(double amount) { strategy.pay(amount); } }

客户端使用时,只需要选择策略、注入上下文、调用方法:

PayContext context = new PayContext(); context.setStrategy(new WechatPay()); context.executePay(99.0);

每个支付方式是一个独立策略类,新增加一种支付方式,就是新增一个类,原来所有代码都不用动。这就是“开闭原则”实打实的体现。

策略模式的本质是把算法族封装起来,让它们可以互相替换。JDK里最典型的例子是Comparator,你给Collections.sort传入不同的Comparator实现,排序算法骨架不变,具体比较策略却可以千变万化。

这里容易和工厂模式搞混:工厂是决定“创建谁”,策略是决定“执行谁”。支付场景里,工厂解决“按类型创建哪个支付对象”,策略解决“创建出来之后怎么算法化地执行”。两者经常配合使用:工厂负责按条件创建策略对象,策略对象负责具体的算法执行。面试时能说出这一层配合关系,是加分项。

3.4 观察者模式:一对多通知的原子弹

观察者模式解决的是“一对多依赖”问题:一个对象状态变了,所有依赖它的对象自动收到通知。经典的例子是气象站,气象数据一变,显示屏、手机App、网站都要同步更新。

手写一个最简单版本:

public interface Observer { void update(float temperature); } public interface Subject { void registerObserver(Observer observer); void removeObserver(Observer observer); void notifyObservers(); } public class WeatherData implements Subject { private List<Observer> observers = new ArrayList<>(); private float temperature; @Override public void registerObserver(Observer observer) { observers.add(observer); } @Override public void removeObserver(Observer observer) { observers.remove(observer); } @Override public void notifyObservers() { for (Observer observer : observers) { observer.update(temperature); } } public void setTemperature(float temperature) { this.temperature = temperature; notifyObservers(); } }

这段代码里,WeatherData不关心观察者到底是谁、要做什么,它只负责遍历通知。新增一个观察者,不需要改WeatherData,只要实现Observer接口并注册进来就行。这就是观察者模式的核心价值:发布者和订阅者之间解耦

如果你接触过消息队列,会发现观察者模式就是发布-订阅模式的同步版。Spring里的ApplicationEvent+@EventListener就是观察者模式的事件机制。你在Service里发布一个订单创建事件,邮件服务、短信服务、积分服务各自监听这个事件,彼此互不干扰。

套用一句话:观察者模式关心的是“东西变了,怎么让所有相关方都知道”

3.5 模板方法模式:固定流程,多变细节

模板方法模式太好用了,而且你其实早就在用它,只是没意识到。它的思路是:父类定义一个算法的骨架,把其中某些步骤延迟到子类实现。子类不能改变算法结构,但可以重定义算法里的特定步骤。

经典例子是冲泡饮品。不管泡咖啡还是泡茶,流程都是固定的:烧水、冲泡、加料。其中烧水的逻辑完全一样,冲泡和加料则各不相同:

public abstract class Beverage { public final void prepareRecipe() { boilWater(); brew(); pourInCup(); if (wantCondiment()) { // 钩子方法 addCondiment(); } } protected abstract void brew(); protected abstract void addCondiment(); private void boilWater() { System.out.println("烧水"); } private void pourInCup() { System.out.println("倒入杯子"); } // 钩子方法:子类可覆写,决定是否执行某步骤 protected boolean wantCondiment() { return true; } } public class Tea extends Beverage { @Override protected void brew() { System.out.println("泡茶叶"); } @Override protected void addCondiment() { System.out.println("加柠檬"); } }

注意那个wantCondiment()方法,它叫钩子方法。钩子方法的妙处在于,父类留了一个“可选项”给子类,子类通过覆写钩子方法,可以灵活决定算法中的某些步骤要不要执行。比如今天不想加料,就返回false

模板方法模式在框架源码里太常见了。Spring的JdbcTemplate把“获取连接、执行SQL、处理结果集、关闭连接”这个固定流程全部封装好,只把RowMapper(结果集如何映射成对象)留给开发者实现。AbstractQueuedSynchronizer(AQS)也是典型代表,同步器的获取锁骨架固定,子类只需要实现tryAcquiretryRelease这些细节。

什么时候用模板方法?当你发现一段流程反复出现,大部分逻辑一样,只有中间几步不同的时候。合并重复代码、抽出骨架、让变化的部分开放给子类,这就是模板方法的价值。

3.6 装饰器模式 vs 代理模式:长得像,意图差之千里

这两个模式非常容易混淆,因为它们在代码结构上都是“把一个对象包在另一个对象里面”。但意图完全不同:

  • 装饰器模式:动态地给对象添加职责,比如给咖啡加奶、加糖。
  • 代理模式:控制对对象的访问,比如明星的经纪人,并不给明星加才艺,而是替明星挡事情。

看代码就清楚了。装饰器模式的典型写法,Java IO流里到处都是:

BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.txt"));

BufferedInputStream装饰了FileInputStream,给原本的文件流增加了缓冲能力。你还可以再包一层DataInputStream,增加读基本数据类型的能力。一层套一层,功能叠加,这就是装饰器。

而代理模式,以最简单的静态代理为例:

public class Proxy implements Subject { private RealSubject realSubject; @Override public void request() { System.out.println("访问前做一些控制"); realSubject.request(); System.out.println("访问后做一些处理"); } }

代理强调的是控制访问,代理持有一个真实对象,在调用真实对象前后可以加权限校验、日志记录、性能统计等等。Spring AOP的底层就是动态代理,你声明一个@Transactional,框架在运行时给你生成一个代理对象,在方法调用前后帮你开启和提交事务。

面试被问“装饰器和代理有什么区别”,我给你一个稳妥的答题结构:先说共同点——都是包装模式,结构上都是持有另一个对象;再说核心区别——装饰器专注于增强功能,代理专注于控制访问;最后举例,IO流是装饰器,Spring AOP是代理。这样答既有层次又有落地场景。

剩下的模式里,建造者模式在对象字段极多时非常好用,Lombok 的@Builder就是它;状态模式适合“对象有多个状态、每个状态行为不同”的场景,比如订单状态机;责任链模式适合过滤器、审批流,MyBatis 的拦截器链、Servlet 的 FilterChain 都是它的实例。有需要时按场景去查即可,核心思想你已经有了。

4. UML类图怎么读:看得懂,才能聊得清

4.1 类图的六种关系,先记住画法

网上讨论设计模式时,动不动就贴UML类图。很多零基础同学看到一堆箭头直接劝退。其实UML类图就六种关系,搞懂画法,剩下的全是看图说话。

关系箭头表示记忆关键词
泛化(继承)空心三角箭头 + 实线,指向父类“是”一种,继承
实现空心三角箭头 + 虚线,指向接口“实现”接口
关联普通实线箭头知道对方存在,长期持有
聚合空心菱形 + 实线箭头整体和个体,可分离,如学校和老师
组合实心菱形 + 实线箭头整体和部分,同生共死,如人和心脏
依赖虚线箭头临时用到对方,如方法参数

聚合和组合最让人头大。我的记忆办法是:空心菱形是“松耦合的拥有”,实心菱形是“强绑定的拥有”。学校倒了,老师还是老师,这是聚合;人没了,心脏的意义也就没了,这是组合。

画法记牢之后,看类图就能快速提取信息:箭头从哪指向哪,谁依赖谁,谁继承谁,一目了然。

4.2 举一个例子:观察者模式的类图关系

我们不画图,用文本把观察者模式的类关系列一遍,你看完就能举一反三:

Subject(接口) <|-- WeatherData(实现) Subject 定义了:registerObserver / removeObserver / notifyObservers Observer(接口) <|-- DisplayA(实现) Observer(接口) <|-- DisplayB(实现) Observer(接口) <|-- DisplayC(实现) WeatherData 会持有 List<Observer>,这是“关联关系” WeatherData 调用 Observer.update(),这是“依赖关系”

看一眼这样的文本类图,你就能明白:WeatherData面向Observer接口编程,它并不关心到底是DisplayA还是DisplayB,这就是依赖倒置原则在类图上的体现。面试时如果让你画观察者模式类图,你只要把这四行关系画对,思路就是清晰的。

4.3 从类图反推设计模式的方法

有时候面试官不直接问“什么是策略模式”,而是给你一张UML类图,让你判断这是什么模式。这时候别慌,看几个关键特征:

  • 有一个接口,底下多个实现类,上下文持有接口引用并调用它——大概率是策略模式
  • 接口有多个实现类,并且接口定义了某个算法的骨架,子类只改部分步骤——大概率是模板方法模式
  • 一个类持有大量订阅者接口,状态变化时循环调用订阅者的方法——大概率是观察者模式
  • 一个类通过构造器接收另一个类,层层包裹,每次包一层增加功能——大概率是装饰器模式
  • 一个类和另一个类实现同一个接口,并且该类内部持有同接口类型的真实对象——大概率是代理模式

这个反推能力特别重要,软考和期末考喜欢出这种题。练法也很简单:把23种模式的UML图全部过一遍,不看图例,只看类之间的关系,自己说一遍结构特征。说不上来的,回头再查,两三轮下来,你看到任何类图都能秒识别。

5. 应用场景与面试题实战

5.1 模式-场景速查表:工作中直接翻

很多同学学完设计模式最大的困惑是“学了不知道往哪用”。我整理了一份高频场景速查表,你遇到类似需求直接对号入座。

需求场景推荐模式理由
某个类全局只能有一个实例单例避免重复创建,保证状态一致
不想在业务代码里直接 new 对象工厂方法/抽象工厂创建过程解耦,替换方便
创建对象字段太多,参数容易搞错建造者链式调用,可读性好
对象创建成本高,很多地方要复用同一批对象享元池化思想,复用实例
一套算法可以在运行时灵活切换策略算法独立封装,互相替换
日志输出、缓存操作、权限控制等横切逻辑代理不侵入业务代码,统一控制
给对象动态增强功能装饰器比继承灵活,组合优于继承
新旧接口不兼容,需要桥接适配器转换接口,让两边正常协作
复杂子系统只需暴露一个简单入口外观门面封装,降低使用成本
多个状态对应不同行为状态状态转移清晰,消除大量if
请求需要多个处理器依次处理责任链解耦发送者和接收者
对象状态变化需要通知一堆对象观察者一对多广播,发布订阅
固定流程,部分步骤各异模板方法骨架复用,细节交给子类
需要记录操作历史并支持撤销备忘录+命令保存快照,命令封装操作

这张表记熟,日后面试题里“这个场景用什么模式”基本难不倒你。

5.2 高频面试题和答题思路

面试题是设计模式学习的重要试金石。我把最高频的几类问题整理一下,并给出参考回答框架。

问题一:手写一个线程安全的单例。

这是最基础但挂了无数人的题。建议背熟双重检查锁写法,并且主动解释volatile的作用,再补一句“更推荐用枚举或静态内部类”。主动说关键点,面试官会觉得你理解得深。

问题二:Spring里用到了哪些设计模式?

这是一道综合性考题,答得好非常出彩。参考回答:Bean默认单例是单例模式;BeanFactory是工厂模式;AOP用动态代理;JdbcTemplate用模板方法;ApplicationEvent用观察者;HandlerInterceptor用责任链;MyBatisSqlSessionFactory是工厂,Executor骨架是模板方法等等。你不需要把Spring所有源码都背下来,挑三四个你能讲清场景的例子就够了,关键是说明“在哪个模块、解决什么问题”。

问题三:装饰器模式和代理模式的区别。

这个上面已经讲透。答题要点:都是包装,但意图不同,装饰器增强功能,代理控制访问;举例说明。

问题四:如果订单支付方式特别多,你怎么设计?

这是一个典型的场景设计题,零基础也能答。参考思路:定义支付策略接口,每种支付方式一个实现类;用简单工厂或工厂方法根据支付方式类型创建策略;来源数据变化时通过观察者通知订单状态的变更;如果支付流程有很多步骤(验签、调第三方、回调、更新库存),用模板方法把骨架固定。能答到这个层次,面试官基本认可你具备设计意识。

问题五:如何消灭项目里满天飞的if-else?

别一上来就说“全部用设计模式”,这是过度设计的味道。建议答案是:先按场景分类,如果if-else在根据类型创建对象,用工厂方法;如果if-else在根据类型执行不同算法,用策略模式;如果if-else在根据状态切换行为,用状态模式;如果分支后面还会继续加,优先考虑模式重构。如果分支是稳定的业务规则,硬套模式反而增加复杂度。

问题六:为什么要面向接口编程?

这道题考察的是设计原则。参考回答:面向接口编程是依赖倒置原则的体现。调用方不依赖具体实现,只依赖抽象,这样实现类可以随时替换,系统扩展性和可测试性都大幅提升。配合策略模式、工厂模式举例,说服力更强。

5.3 期末、软考、大作业:怎么临时抱佛脚也抱出水平

如果你是赶期末或软考,时间不多,我建议按下面的顺序执行:

第一步:先把第2.2节的分类表和口诀背下来,做到看到模式名能说出它是创建型、结构型还是行为型,这一两天就能做到。软考和期末的选择题、判断题很喜欢考分类。

第二步:重点画三个类图关系:策略模式、观察者模式、模板方法模式。这三个出图题的几率最高。我就见过期末考“画出观察者模式UML类图并简述各角色职责”的题目,上面4.2节的文本关系你背下来,画图题基本稳了。

第三步:每个模式准备一个“一句话场景”。比如“某系统需要在运行时切换加密算法,用什么模式?——策略模式”。软考案例分析特别喜欢这种模式识别题,你把我那张速查表过两遍,识别题就能应付。

第四步:如果是大作业,选题策略是选一个“天然适合展示多种模式”的系统。比如在线商城,支付用策略模式,订单创建过程用建造者模式,库存扣减通知用观察者模式,登录拦截用责任链模式。写代码前先画出类图,再把类图对应到每一个模式,代码实现起来反而更快。大作业的重点不是代码量,是“设计思路清晰、模式应用有理有据”,一份类图加一份模式应用说明,比硬写几千行代码更得分。

6. 常见问题与避坑技巧

6.1 误区一:背得出23个名字,却说不出应用场景

这种“纸上谈兵”型学习者特别多。症状是:口诀背得滚瓜烂熟,面试官问“你项目里哪里用了观察者模式”立刻卡壳。

破解方法只有一个:强迫自己每个模式都去真实代码里找一个案例。比如单例——你项目里的配置文件读取工具类是不是应该单例?策略——你项目里有没有一套算法或规则经常要换?观察者——你项目里有没有“某个事件发生后要通知很多模块”的场景?找到一个,写下来,画个图,这个模式就长在你脑子里了。

6.2 误区二:为了用而用,过度设计

刚学会设计模式的人很容易“手里拿着锤子,看什么都像钉子”。一个只有十几个方法的Contoller,硬拆成工厂+策略+观察者,结果代码量翻倍,可读性暴跌。

我的原则是:没有变化的需求,就不要套模式。设计模式是应对变化的,如果某个分支未来三年都不会变,老老实实写if-else完全没问题。什么时候重构?当你发现加需求越来越累、改代码容易改坏的时候,再引入模式。重构的时机比模式本身更重要。

6.3 误区三:只看不练,类图不会画,代码写不出

设计模式是技能,技能就得靠练。我建议你手上的练习项目至少包含这几个模式:支付模块用策略+工厂,日志通知用观察者,数据导入流程用模板方法,权限拦截用代理或责任链。不用面面俱到,但每个模式至少要自己完整写一遍,并画出对应的UML类图。写一遍,比看十遍都管用。

6.4 我在实际项目里是怎么识别重构契机的

说点实战经验。我通常会在三种情况下考虑引入设计模式:

第一种,if-else超过三层,而且分支还在不断增加。比如支付方式每季度加一种,这时候不抽策略模式,代码会越来越难读。第二种,一个类里职责特别多,改动一个点会影响一片。这时候按单一职责拆类,搭配观察者或命令模式理清交互。第三种,多个类里有重复的流程代码,比如每个Service都要做“参数校验-权限校验-业务处理-日志记录”,抽个模板方法或责任链,省掉大量重复劳动。

判断标准始终是“是否降低了修改成本”。如果引入模式后,下一次加需求更轻松了,那就是对的;如果只是把简单问题复杂化,赶紧退回去。设计模式没有银弹,适合的才是最好的。

6.5 面试手写代码时的三个小细节

最后聊点面试手撕代码的细节。写单例的时候,一定要写volatile,哪怕面试官没提示,你主动加上并解释原因,这题就基本满分了。写策略模式的时候,记得展示“策略接口 + 上下文 + 客户端”三层结构,漏掉上下文,策略模式的价值就体现不出来。写观察者模式的时候,注意让发布者面向观察者接口编程,不要在发布者里 hard code 具体观察者类。这三个细节,都是面试官考察你“到底是背模板还是真理解”的分水岭。

我自己学设计模式最大的体会是:它不是一个需要“精通”的学科,而是一套用来解决问题的工具箱。你不需要也没必要把23种模式全部背得滚瓜烂熟,你只需要知道每个箱子里装的是什么,遇到问题知道该去开哪个抽屉,就够了。用得多了,你会慢慢发现,很多模式不是设计出来的,而是重构出来的——一开始代码都很朴素,随着需求不断变化,那些优秀的设计结构会自然浮现。如果你也有学了不用、看了就忘的经历,别焦虑,先从手边最简单的一个if-else开始,试着把它换成一个策略模式,你会感受到那个“从此不再改旧代码”的爽快感。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询