ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

设计模式实战指南:23种经典模式一网打尽

设计模式实战指南:23种经典模式一网打尽 设计模式这词圈外人听着像什么高深武功秘籍圈内人聊起来又是毁誉参半。有人觉得它是 Java 面试必考题、软考必背点、期末大作业的救命稻草也有人觉得满脑子单例工厂观察者背了一堆真写业务代码时压根用不上纯属自嗨。我的观点比较直接设计模式不是银弹但确实是程序员从能写代码迈向会写代码的关键台阶。这篇文章就带你把这 23 种经典设计模式拆开揉碎不讲晦涩的 UML 图不堆理论名词而是从这段代码为什么这么写的实战视角把每个模式的适用场景、核心思路、代码骨架和常见坑位一次说清楚。不管是零基础入门、面试突击还是软考速记、期末突击都可以直接对照抄作业。1. 设计模式到底在解决什么问题在聊具体模式之前得先把模式这个词本身讲明白。很多人一听 23 种模式就头大觉得这是某种必须全部背下来的知识清单。其实完全不是这么回事。1.1 什么是设计模式从乐高积木说起你小时候玩过乐高吧乐高的每块积木都有固定规格但它们能拼出城堡、飞机、汽车甚至整个城市。设计模式干的事就是给你一套积木拼法说明书——告诉你哪些积木搭在一起最稳、哪些接法容易散架、哪些结构方便后面加高加宽。比如你在项目中经常遇到这种情况一段业务逻辑要创建不同类型的产品对象用 if-else 写了一遍又一遍。每次新增产品类型就要去改动原来的代码改着改着代码越来越臃肿测试也越来越不敢动。设计模式里的简单工厂工厂方法抽象工厂就是专门解决这类问题的——把创建对象这部分逻辑抽出来单独做一个工厂来管理。你后续加新产品只改工厂不动业务代码。说白了设计模式是无数前辈在实际项目中踩了无数坑之后总结出的常见问题的标准解决方案。它不规定你每条代码怎么写而是给你一种通用的思路框架让你面对变化时不会手忙脚乱。1.2 没有设计模式的项目是什么样加班改 Bug 的源头说个我自己经历的案例。早年间在一家传统软件公司做进销存系统当时的代码风格是典型的一个 Service 处理所有业务。商品入库、出库、库存查询、报表统计全部塞在一个类里方法一个接一个类文件三千多行。印象最深的是有一次改库存预警的逻辑。需求很简单当库存低于某个阈值时邮件通知管理员。当时我找那个告警方法找了半天好不容易改完结果发现下游的报表统计也被影响了——因为告警方法里顺手改了个库存状态字段报表模块依赖这个字段。如果当时用了观察者模式把库存变化作为事件源邮件通知、报表更新、日志记录都作为观察者订阅这个事件我只需要新增一个观察者根本不用动原来的任何代码也就不会出现按下葫芦浮起瓢的情况了。这就是设计模式的价值它不是让你炫技而是让代码在需求不断变化时依然能够可控、可维护、可扩展。1.3 设计模式适合哪些人学零基础小白刚开始学编程时接触设计模式理解起来确实吃力但可以先建立一个代码还可以这样组织的概念知道有这些套路存在之后写多了自然会用上。准备面试的求职者设计模式可以说是 Java 后端面试的高频考点尤其单例、工厂、代理、策略这几种不仅会问原理还经常让手写代码。软考/期末考试学生23 种模式的口诀、分类、适用场景属于必背内容但光背不行还得能看懂类图和代码。工作中的业务开发者这类人是最能体会到设计模式价值的群体——当你面对一坨需要频繁变动的老代码你就知道你急需用模式来理顺它。2. 先吃透六大设计原则模式的心法23 种设计模式是招式而六大设计原则是心法。招式可以一招一招练心法却是贯穿所有招式的灵魂。不夸张地说把六大原则理解透了你不背模式也能写出优雅的代码。2.1 开闭原则对扩展开放对修改关闭开闭原则是所有设计模式的核心精神一句话解释当需求变化时尽量通过新增代码来实现新功能而不是改动原有代码。举个例子。你写了一个订单金额计算器原本只支持普通订单。现在要加一个 VIP 订单VIP 享受 9 折优惠。糟糕的写法是public double calcDiscount(Order order) { if (order.getUserType().equals(NORMAL)) { return order.getAmount(); } else if (order.getUserType().equals(VIP)) { return order.getAmount() * 0.9; } return order.getAmount(); }这个写法在新增超级 VIP时你还得再来一个 else if每加一个用户类型这个类就得改一次改多了就容易把普通用户的逻辑改出 Bug。符合开闭原则的写法是定义一个折扣策略接口普通、VIP、超级 VIP 各写一个实现类。以后加新的用户类型新增一个类就行原代码一行不动。这就是对扩展开放对修改关闭。2.2 里氏替换原则子类尽量不要改变父类的行为里氏替换原则听起来拗口核心就一句话能使用父类的地方替换成子类后程序依然能正确运行。写代码时经常有人图省事继承父类后把父类的方法直接覆写改个面目全非。比如定义了一个Bird类有fly()方法后来要加一个企鹅子类发现企鹅不会飞于是在子类里把fly()实现成一个空方法。看起来挺合理但你的业务代码如果循环调用所有Bird的fly()企鹅就静悄悄什么也不干可能会导致某些逻辑静默失败。里氏替换原则告诉你企鹅不应该继承会飞Bird应该继承一个更通用的Animal类或者把会飞抽成一个接口让会飞的鸟类去实现。继承要慎用接口才是更好的解耦方式。2.3 依赖倒置原则面向接口编程别面向实现编程依赖倒置原则说的是高层模块不应该依赖低层模块两者都应该依赖抽象抽象不应该依赖细节细节应该依赖抽象。说白了就是——定义变量时能用接口类型就不用具体实现类型。比如ListString list new ArrayList();而不是ArrayListString list new ArrayList();前者方便以后换成 LinkedList不用改动调用代码。这个原则看似简单但在项目架构设计时至关重要。你的 Service 层调用 DAO 层最好依赖 DAO 的接口而不是具体的 DAO 实现类这样以后换数据库、换 ORM 框架代价就小很多。2.4 接口隔离原则别让一个接口承担太多职责接口隔离原则用一句话说客户端不应该被迫依赖它不使用的方法。经常看到有人定义一个超级接口里面塞了十来个方法实现类明明只需要其中三个却必须把其余七个空实现。这就是接口设计不合理。正确做法是把接口拆小。比如一个Worker接口有work()、eat()、sleep()三个方法但机器人只需要work()那你应该拆成Workable和Eatable让不同实现类按需实现。2.5 迪米特法则最少知道原则别和陌生人说话迪米特法则强调一个对象应该对其他对象保持最少的了解。翻译成写代码的原则就是不要在一个方法里层层调用a.getB().getC().getD().doSomething()这种链式调用每一层都产生了强耦合中间任何一层发生变化整条链就全崩了。更好的做法是在A中直接提供一个方法帮你把整件事做完避免串联调用。这也是很多数据对象设计 DTO/VO 的原因——你不需要知道内部怎么组织的只要拿到结果就行。2.6 单一职责原则一个类只负责一件事单一职责原则是高内聚低耦合的直接体现。一个类只做一件事并且把这件事做好。回到前面那个所有业务写在一个 Service的反面案例。订单 Service 又管订单校验、又管库存扣减、又管优惠计算、又管消息通知任何一个小需求变化就得改这个类测试也得反复回归。拆成OrderValidator、StockService、DiscountCalculator、NotificationService各司其职之后每次改动影响范围就小很多。六原则是设计模式的指导思想接下来 23 种模式基本都是为了落实这些原则而产生的具体方案。3. 创建型模式对象应该怎么生出来创建型模式关注的是对象的创建方式。你可能会说创建对象不是 new 一下就行吗但在复杂系统中怎么创建恰恰是最容易产生重复代码和耦合的地方。3.1 单例模式全局只允许一个实例单例模式是最简单又最常考的模式核心思想是确保一个类只有一个实例并提供一个全局访问点。什么地方用单例线程池、缓存、配置对象、数据库连接池这些资源全局只需要一份多创建反而浪费。经典的双重检查锁写法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因为instance new Singleton()并不是原子操作它分三步——分配内存、初始化对象、把引用指向内存。如果不加volatileJVM 可能重排序另一个线程读到一个半初始化的对象。提示工作中尽量用枚举方式实现单例代码简洁且天然线程安全。但要留意单例用多了会变成全局状态测试时难以 mock别什么类都往单例上套。3.2 工厂方法模式把创建的责任交给子类工厂方法模式解决的核心问题是代码里频繁出现 new 具体类导致扩展困难。比如你开发一个日志系统支持文件日志、数据库日志、控制台日志。如果代码里到处是new FileLogger()新增一种日志类型时就得全局搜索替换。工厂方法模式的做法是定义一个日志工厂接口文件日志工厂、数据库日志工厂分别实现它。客户端只依赖工厂接口具体创建哪个日志对象由工厂决定。public interface LoggerFactory { Logger createLogger(); } public class FileLoggerFactory implements LoggerFactory { Override public Logger createLogger() { return new FileLogger(); } }这样新增一种日志类型时只需新增一个工厂类不用改动已有代码完美符合开闭原则。3.3 抽象工厂模式创建产品族抽象工厂是工厂方法的升级版。工厂方法解决单个产品的创建抽象工厂解决一组相关联产品的创建。最常见的例子是 UI 组件库。假设你在开发一套跨平台界面需要按钮、输入框、弹窗。在 Windows 风格下一套实现Mac 风格下另一套实现。如果为每个组件单独建工厂就会出现Windows 按钮 Mac 输入框这种混搭的灾难。抽象工厂的做法是先定义UIFactory接口里面有createButton()、createInput()、createDialog()三个方法。然后写WindowsUIFactory和MacUIFactory各自实现整套 UI 组件。客户端获取 UI 组件时只需要知道当前是哪个工厂就能拿到一整套风格一致的组件。3.4 建造者模式复杂对象的分步组装建造者模式适用于创建参数特别多的复杂对象。想象一下你要创建一个电脑订单需要 CPU、内存、硬盘、显卡、显示器……十来个参数如果直接用构造函数参数顺序错一位就出 Bug。建造者模式的做法是把复杂的构造过程拆分成多个步骤客户端可以按需调用步骤最后通过build()生成最终对象。用链式调用实现最舒服Computer computer new Computer.Builder() .cpu(i7-13700K) .memory(32G) .disk(1T SSD) .gpu(RTX 4070) .build();很多框架里的Builder就是这么干的比如 Lombok 的Builder注解本质上就是自动帮你生成建造者代码。3.5 原型模式克隆已有对象原型模式的核心是通过复制已有对象来创建新对象而不是重新 new 一个。什么时候用到当对象创建成本很高比如经过复杂计算、数据库查询、网络请求而你只是需要几个类似的实例时直接拷贝最快。Java 中实现原型模式有两种方式实现Cloneable接口重写clone()或者序列化/反序列化。第一种要注意对象内部引用类型的深拷贝问题——浅拷贝只会复制引用修改一个对象的内部字段另一个也会受影响。public class Prototype implements Cloneable { private ListString list new ArrayList(); Override public Prototype clone() { Prototype prototype (Prototype) super.clone(); prototype.list new ArrayList(this.list); // 深拷贝 return prototype; } }3.6 创建型模式小结创建型模式的共同点是把创建对象这个不稳定因素隔离出来。你代码里凡是出现高频的new Xxx()都值得思考一下是不是应该用工厂管理一下。4. 结构型模式类和对象如何组装更合理结构型模式解决的是如何组织类和对象让代码结构更清晰、复用性更强。这些模式在平时开发中出场率极高。4.1 适配器模式把不兼容的接口接起来适配器模式就像你手机充电器上的转接头。现有代码提供的是 A 接口客户需要的是 B 接口两者不兼容那就加一个适配层让 A 以 B 的形式提供服务。很多老系统的改造都会用到适配器。比如公司在用一个旧版消息推送 SDK接口叫send(String msg)新的业务系统统一调用push(PushRequest req)你不可能去改 SDK那就写一个适配器类实现新接口内部调旧 SDK 的send方法。public class MessageAdapter implements NewPushService { private OldSdk oldSdk; Override public void push(PushRequest req) { oldSdk.send(req.getContent()); } }对外新系统的调用方完全不知道底层其实是老 SDK将来替换新 SDK 时也只需要改这个适配器类。4.2 装饰器模式给对象加装备但不改源码装饰器模式让你在不修改原有类代码的情况下动态地给对象增加新功能。理解这个模式最好的例子是咖啡点单咖啡是基础对象加奶、加糖、加珍珠都是往基础对象上装饰。Java 的 IO 流就是装饰器模式最经典的例子。new BufferedInputStream(new FileInputStream(a.txt))BufferedInputStream就是给FileInputStream加了一层缓冲功能里面的原对象根本感觉不到自己被装饰了。用代码表示就是装饰器类和被装饰类实现同一个接口装饰器内部持有被装饰对象的引用在调用原方法前后插入增强逻辑。public class SugarDecorator implements Coffee { private Coffee coffee; public SugarDecorator(Coffee coffee) { this.coffee coffee; } Override public String getDesc() { return coffee.getDesc() 糖; } }装饰器模式和继承的区别在于继承是在编译期静态确定功能组合是写死的装饰器是在运行时动态叠加灵活得多。缺点是代码层级多了之后不好排查容易让调试变成解谜游戏。4.3 代理模式控制访问的门卫代理模式的核心思想是不直接操作目标对象而是通过一个代理对象间接操作由代理对象控制访问权限、增加附加操作。最典型的应用就是 Spring AOP。你在方法上加了TransactionalSpring 会创建一个代理对象在调用真实方法之前开启事务在方法返回之后提交事务在方法抛异常之后回滚事务——你写业务代码的人完全感知不到这个过程。静态代理需要手动写代理类动态代理用 Java 的Proxy或 CGLIB 在运行时生成代理类。面试常问这两者的区别类型原理限制JDK 动态代理基于接口目标类必须实现接口CGLIB 代理基于继承生成子类目标类不能是 final框架里到处是代理模式MyBatis 的 Mapper 接口、Spring 的声明式事务、RPC 框架的远程调用本质都是代理。所以学透代理模式你对主流框架的理解会上升一个档次。4.4 外观模式给复杂子系统提供一个简单窗口外观模式又叫门面模式它的核心是为复杂子系统提供一个统一的高层接口让客户端调用更方便。你去餐厅吃饭不需要知道后厨怎么洗菜切菜炒菜只需要看一眼菜单点菜服务员会把整件事搞定。这个服务员就是门面。很多 Service 层接口设计就用到了外观模式的思想。比如你开发一个下单功能背后涉及库存服务、会员服务、优惠券服务、支付服务如果让 Controller 逐个调用这四个服务逻辑全耦在 Controller 里了。正确做法是提供一个OrderService门面内部编排这四个服务Controller 只依赖OrderService。这也是后端的经典分层思想。4.5 桥接模式将抽象与实现解耦让两个维度独立变化桥接模式解决的核心问题是一个类有多个变化维度。比如你要做一个消息发送系统既可以是普通消息、加密消息、加急消息抽象维度又可以通过短信、邮件、App 推送实现维度。如果每个维度都组合一遍就是 3×39 个类而且每加一种渠道或者消息类型类数量就爆炸。桥接模式的做法是抽象维度与实现维度分开中间搭一座桥public abstract class Message { protected MessageSender sender; public Message(MessageSender sender) { this.sender sender; } public abstract void send(); }这样抽象维度消息类型和实现维度发送渠道各自独立扩展互不干扰。组合方式不再是倍数关系而是加法关系。4.6 组合模式树形结构的统一处理组合模式主要解决部分与整体的树形结构问题。最典型的就是公司组织架构部门里有员工也可以有子部门文件夹里有文件也可以有子文件夹。组合模式的关键是让叶子节点和容器节点实现同一个接口客户端可以一致对待单个对象和组合对象。比如文件系统类库无论是单个文件还是整个文件夹对外都支持getSize()方法文件夹的 size 是所有子项的 size 之和——客户端根本不需要判断当前操作的是文件还是文件夹。4.7 享元模式共享对象节省内存享元模式的核心是尽可能多地共享细粒度对象避免创建大量重复对象。这在游戏开发中极其常见。一个战场里有上千个士兵模型每个士兵如果都单独创建一个对象内存瞬间爆炸。如果把士兵的外观模型、动作动画做成共享的享元对象每个士兵只保存自己的位置、血量这些独特状态内存占用就能大幅降低。Java 中的Integer缓存就是享元模式的应用。Integer.valueOf(127)和Integer.valueOf(127)返回的是同一个对象而超过 127 就会 new 新对象所以用比较整数时经常出现诡异结果本质就是享元缓存的边界问题。4.8 结构型模式小结结构型模式的核心命题是解耦适配器解耦接口不匹配装饰器解耦继承关系代理解耦访问逻辑外观解耦子系统依赖桥接解耦多维度变化组合解耦树形结构享元解耦内存消耗。基本上哪里的代码让你觉得关系错综复杂、不敢改动哪里就住着一个结构型模式的需求。5. 行为型模式对象之间怎么协作更优雅如果说创建型模式管对象的出生结构型模式管对象的组装那行为型模式管的就是对象之间的协作与职责分配。这一类的模式最多也最贴近业务开发。5.1 模板方法模式把固定流程定死把变化交给子类模板方法模式是非常符合直觉的一种模式在父类中定义一个操作中的算法骨架把某些步骤延迟到子类中实现。举个例子做菜流程大体是固定的备菜 → 炒制 → 装盘。但炒制这一步麻婆豆腐和西红柿炒蛋是截然不同的。于是你定义一个抽象的CookTemplate类备菜和装盘通用方法cook()定义为抽象方法让子类实现。模板方法模式的好处是流程复用并且确保所有子类都遵守统一流程不会有人漏了某一步。在很多框架源码里模板方法模式无处不在比如 Spring 的JdbcTemplate内部定死了获取连接、执行 SQL、关闭资源的流程把写 SQL 这一步留给你。5.2 策略模式把可以互相替换的算法封装起来策略模式应该是业务代码中最实用的模式之一它的核心是定义一系列算法把每个算法封装起来并使它们可以互相替换。回到开闭原则里那个订单折扣的例子。按用户类型打折、按满减活动打折、按会员等级打折这些都是策略。用策略模式实现public interface DiscountStrategy { double calc(double amount); } public class VipDiscount implements DiscountStrategy { Override public double calc(double amount) { return amount * 0.9; } } public class FullReductionDiscount implements DiscountStrategy { Override public double calc(double amount) { return amount 300 ? amount - 50 : amount; } }以后要加一个新活动写一个新策略类在配置里指定即可原来的代码一个字节都不用动。我特别推荐大家在业务代码中多考虑策略模式。很多人觉得 if-else 没什么大不了的但随着分支越来越长、条件越来越乱总有一天你会后悔当初没做抽象。策略模式配合枚举和 Map 使用效果极佳。5.3 观察者模式发布订阅事件驱动编程的基石观察者模式的核心是当被观察对象的状态发生变化时所有依赖它的观察者对象都会得到通知并自动更新。最生活化的例子就是订阅公众号你关注了一个博主博主发了新文章微信会推给你。你不需要天天打开博主的页面去看有没有更新博主也不需要知道你到底关没关注。代码层面一般这样设计一个Subject接口管理观察者的注册、移除和通知观察者实现共同的update()接口。比如前面说的库存预警需求public class StockSubject { private ListObserver observers new ArrayList(); public void attach(Observer observer) { observers.add(observer); } public void notifyObservers(Stock stock) { for (Observer observer : observers) { observer.update(stock); } } }库存变化时调用notifyObservers()邮件观察者发邮件报表观察者更新数据日志观察者记日志。每加一种下游响应只需新增一个观察者注册进去完美符合开闭原则。Spring 中的ApplicationEvent/EventListener就是观察者模式的落地实现。写业务时遇到某个动作之后要连带做好几件事的场景优先考虑观察者模式能显著降低耦合。5.4 职责链模式把请求沿链路传递直到有人处理职责链模式是让多个对象都有机会处理请求形成一条链请求沿着链传递直到有一个对象能处理它。现实中的例子很多请假审批流程三天以内组长批七天以内经理批七天以上总经理批。把审批人串成一条链请假申请从链头开始传谁有权限谁处理。public abstract class Approver { protected Approver next; public void setNext(Approver next) { this.next next; } public abstract void approve(int days); } public class Manager extends Approver { Override public void approve(int days) { if (days 7) { System.out.println(经理审批通过); } else if (next ! null) { next.approve(days); } } }职责链模式的优势是把请求的发送者和接收者解耦每个处理者只需关心自己能不能处理处理不了就往下传。5.5 状态模式把状态和对应行为封装成类状态模式和策略模式结构上很像但目的不同。策略模式让对象自由切换算法状态模式则是让对象在内部状态改变时其行为也随之改变。最经典的例子是订单状态流转。订单有已下单、已支付、已发货、已完成、已取消等状态不同状态下退款操作的行为完全不同已下单可以退款已发货需要走退货流程已完成则只能走售后。如果用一个类写 if-else 处理所有状态代码会越来越硬。状态模式的做法是把每种状态封装成一个类订单对象持有当前状态对象调用状态对象的方法状态变化时替换当前状态对象。这样订单类变得非常简洁每个状态类只负责自己的逻辑。缺点是状态类数量多但每一个都很纯粹。5.6 其他行为型模式速览行为型模式一共 11 种除了上面 5 种还有 6 种。这些不一定所有场景都用得上但面试和软考会考我也简单过一遍迭代器模式提供一种方法顺序访问集合中的各个元素而不暴露集合内部表示。Java 的Iterator接口就是典型实现。访问者模式当某个对象结构比如树形结构中的元素变化较少但要对这些元素执行的操作变化很多时把操作封装成访问者。这个模式理解起来略费劲实际业务中使用频率也较低但软考常考。备忘录模式在不破坏封装性的前提下捕获并保存一个对象的内部状态以便之后可以恢复。游戏存档机制就是备忘录模式的经典应用。中介者模式用一个中介对象来封装一系列对象之间的交互让各对象之间不再显式引用对方。聊天室就是这个模式每个用户不直接和其他用户通信而是通过服务器中转。命令模式把请求封装成一个命令对象从而可以用不同的请求、队列或日志来参数化其他对象。遥控器上的每个按钮就是一条命令按下按钮发出命令执行者执行具体动作。解释器模式给定一个语言定义它的语法表示并定义一个解释器来解释句子。正则表达式引擎就是解释器模式的典型应用场景。5.7 行为型模式小结行为型模式最贴近业务逻辑。我的经验是如果你的业务代码中出现大量互相纠缠的 if-else先别急着怼需求方试着用策略模式把分支拆掉如果某个动作后要触发多个连锁反应用观察者模式解耦如果同一套流程在不同场景下有不同表现用模板方法模式固定骨架、开放变点。6. 零基础到精通的实战学习路径聊完 23 种模式得说说怎么学的问题。很多人照着书背完一遍隔几天全忘光因为他们学的不是模式是名词。下面这套学习路径是我自己带过不少新人、也踩过不少坑之后总结的应该能帮你少走很多弯路。6.1 第一阶段建立模式地图先骨架后血肉不要指望一次性吃透 23 种模式。第一遍学习你的目标仅仅是看完每种模式的三件事解决什么问题、核心思想是什么、代码骨架长什么样。我建议按这个顺序来先学六大设计原则花两天左右理解核心思想不需要完全掌握至少有个印象。按创建型 → 结构型 → 行为型的顺序每天啃 3~4 个模式。每个模式只做三件事看一个生活化类比、看一段核心代码、记一个应用场景。这阶段不追求深度追求广度。告诉自己这遍看完以后可能还是不会用没关系先混个脸熟。6.2 第二阶段代码复现动手写才是硬道理看别人的代码和自己动手写完全是两码事。每一种模式我建议都亲手敲一遍核心实现不要去复制哪怕只是照着敲也比复制强十倍。每敲完一段问自己三个问题这个模式把哪段变化封装起来了如果没有这个模式代码会遭遇什么问题这个模式的实现有什么缺点适用边界是什么第二个阶段的目标是建立代码手感。你会发现看着简单的单例模式自己写的时候可能会忘记加volatile或者把构造函数写成了 public策略模式写着写着就想偷懒再塞回 if-else。这些坑亲自踩过一遍笔试写代码时才不会再犯。6.3 第三阶段在项目里找模式、用模式过了前两个阶段你脑子里已经有了一堆模式名词但可能还是不知道什么时候该用。这时候最有效的训练方式是在自己的项目代码里找模式——不是去新设计一个系统而是把你已经在跑的代码拿出来试着识别里面的坏味道。比如你发现Controller 里一大段逻辑是先判断用户类型再按不同类型调用不同方法这就是策略模式的潜在需求点。你发现一个方法调用完库存服务后还要调用消息服务、再调用日志服务你大可以把这些调用通过事件解耦那就是观察者模式的落地。每重构一处记录一下原代码是什么样、用了什么模式、改动后带来了什么变化。这种从坏味道到模式的练习比任何教程都有效。6.4 第四阶段源码级理解看大师怎么用模式当你手写了十几遍模式代码有些模式已经能无意识地用了这时可以开始读经典框架源码。Spring、MyBatis、Netty 这些源码里设计模式无处不在。Spring 的BeanFactory是工厂模式加单例模式的结合Spring AOP 是动态代理模式的典范MyBatis 的Configuration大量使用建造者模式Netty 的ChannelPipeline是责任链模式的教科书实现读源码不需要逐行读但每看到一个熟悉的模式骨架都可以停下来琢磨它在实际框架中是怎么落地的加入那些复杂逻辑后核心模式简化成什么样。这种逆向阅读对提升架构能力极其有效。7. 面试、软考、期末考试的速记与实战策略如果你正在准备面试、软考或期末考试上面那套长期学习路径可能来不及。这时候你需要的是定向突击策略。7.1 23 种设计模式记忆口诀为了应对考试背口诀是有用的。这里分享一套帮助你速记的顺口溜按分类整理创建型工厂抽工单原单工厂方法、抽象工厂、建造者、单例、原型 结构型适装代外桥组享适配器、装饰器、代理、外观、桥接、组合、享元 行为型模板策观职责状迭访备中命令解模板方法、策略、观察者、职责链、状态、迭代器、访问者、备忘录、中介者、命令、解释器这套口诀的核心是每个字对应一种模式看到字能想起完整名称就算过了一关。然后再按UML 类图长什么样、核心代码怎么写、典型场景是什么去逐项复习。7.2 软考/期末考试的性价比排序如果你时间紧张先抓性价比高的模式。根据历年的考点分布以下几种模式出现频率远高于其他单例模式几乎必考手写代码 线程安全分析工厂方法/抽象工厂必考容易结合 UML 图出题观察者模式高频重点考事件驱动思想策略模式高频考 if-else 重构模板方法模式中高频考继承结构而访问者模式、解释器模式、享元模式这类偏门的如果时间不够只需要知道定义和适用场景即可不需要研究太深。7.3 Java 面试中被问烂的高频问题这里整理了几个面试高频题你们可以提前准备Q1单例模式有哪几种写法双重检查锁为什么要加 volatile答饿汉式、懒汉式、双重检查锁、静态内部类、枚举。加 volatile 是为了防止指令重排序导致拿到未初始化完全的对象。Q2说一下 JDK 动态代理和 CGLIB 的区别。Spring 默认用哪个答JDK 动态代理基于接口实现目标类必须实现接口CGLIB 基于继承生成子类无法代理 final 类。Spring 优先使用 JDK 动态代理如果目标类没有实现接口则自动切换为 CGLIB。Q3策略模式和状态模式有什么区别答两者类结构类似但意图不同。策略模式是让对象在多个可替换算法中自由选择状态模式是让对象在内部状态变化时自动切换行为。策略的选择通常由客户端决定状态的切换通常由对象自身状态驱动。Q4Spring 中哪些地方用到了设计模式答BeanFactory 是工厂模式Bean 默认单例是单例模式AOP 是代理模式ApplicationEvent 是观察者模式JdbcTemplate 是模板方法模式HandlerInterceptor 是责任链模式转换器是适配器模式。这几道题背熟再配合手写几种核心模式代码面试通过率会明显提升。8. 实操心得那些年我踩过的设计模式坑最后跟大家分享几个真实工作中的踩坑经历每个都是付出过代价才学到的希望你们看完不用再走一遍弯路。8.1 过度设计为模式而模式的惨痛教训刚学设计模式那阵子特别兴奋感觉看谁都是模式。有一次写一个极简单的工具类硬生生套了工厂、单例、策略三种模式封装了三层接口看起来架构感十足。结果组里一个老前辈看代码时愣住了拿着三层封装追问我你这需求就new一下就完事了为什么要绕三层我哑口无言。后来想明白了——设计模式的价值是解决真实存在的问题而不是拿来表演的。如果一段简单的代码有 80% 的把握不会变化那就别引入模式。判断标准很简单当代码出现重复、越来越难改、测试越来越难写的时候才是引入模式的信号否则KISSKeep It Simple, Stupid原则优先。8.2 单例模式引发的线程安全连环坑有一年做定时任务为了方便共享配置我用了一个懒汉式单例但没有加volatile。测试环境跑得好好的上线之后出现了偶发性的空指针异常排查了大半天才发现是并发场景下单例对象初始化未完成另一个线程就拿着半成品对象用了。后来把单例改成饿汉式或者枚举问题立刻消失。经验之谈线程安全问题上能简单就不复杂。饿汉式和枚举能解决 90% 的单例场景别为了优雅去写双重检查锁。8.3 观察者模式过度解耦后的调试噩梦有一段时间我非常执着于解耦几乎把系统里所有联动逻辑都用观察者模式改造了。结果有一天排查一个问题用户下单后短信没发我在下单流程里从头找到尾都没发现短信逻辑最后发现订单状态变更是事件源短信监听器在写日志那层才被通知到中间隔了七八层事件传递调试起来费了九牛二虎之力。观察者模式确实解耦了但也掩盖了调用链。所以在使用观察者模式时一定保证事件名清晰、事件流转有日志记录必要的时候用 traceId 串联全链路。解耦不等于不可追踪分布式系统里尤其要注意。8.4 工厂模式过度抽象导致类爆炸有一年给一个报表系统做重构为了应对未来可能会有几十种报表我给每一种报表都建了专属工厂类。结果实际业务只有三种报表等我做完之后发现连项目里的同事都搞不清楚该用哪个工厂了。**工厂模式的正确用法不是每种类建一个工厂而是当创建逻辑复杂到需要封装时才考虑工厂。**如果只是new一下就能解决的问题别硬造工厂如果创建过程要传一堆参数、要做一堆初始化那工厂就值得出现。9. 给零基础学习者的最后建议写到最后再说点掏心窝子的建议。很多人学设计模式学不下去不是智商问题是方法问题。他们都想着一口气学会全部然后精通设计模式这个心态从一开始就错了。设计模式不是背出来的是烂出来的——你写的代码先烂到一定程度然后才会真正理解某个模式的价值。我自己真正搞懂策略模式是接了一个需求疯狂变化、if-else 疯狂膨胀的项目之后真正搞懂观察者模式是被一个改动牵连三个模块搞到崩溃之后。模式是解决问题的产物你只有真的被问题折磨过才能理解模式为什么那样设计。所以我的建议是第一遍学设计模式看热闹就行知道有这些招式第二遍学结合自己的代码对着例子写一遍第三遍学在工作中遇到坏味道时主动往模式上靠用模式重构。这三遍走完你基本就出师了。最后分享一个所有知识的通用套路也是设计模式的终极心法**封装变化面向接口降低耦合拥抱扩展。**把这十六个字刻在脑子里你写的每一行代码都会慢慢变得有模式味。祝各位早日写出让自己满意的代码也欢迎在评论区分享你学设计模式的经历和踩过的坑。
返回列表