首页 / 资讯中心 / 文章详情

Java设计模式实战:从面向对象到23种模式的核心解析

Java设计模式实战:从面向对象到23种模式的核心解析 ★ FEATURED ARTICLE
1. 先别急着背模式搞清楚它们到底在解决什么问题1.1 设计模式不是什么高深的东西我第一次接触设计模式的时候是在某次项目重构的会议上。那时候一个订单模块的代码堆了两千多行一个类里既有创建订单的逻辑、又有折扣计算的逻辑、还有状态流转的判断改一个需求要小心翼翼翻半天代码。当时的某位前辈看完之后说了一句话你这里应该用策略模式那边用个状态模式就清爽多了。说实话当时我是懵的。策略模式状态模式这些名字听着挺唬人但仔细想想它们其实就是在说一件事把变化的部分从不变的部分里剥离出来。设计模式不是某个天才发明出来的空中楼阁它是前辈们在无数个项目里踩坑踩出来的通用解法。你可以把它理解为程序员界的菜谱红烧肉有很多种做法但万变不离其宗的步骤就是焯水、炒糖色、慢炖。设计模式做的就是这个事——把一类常见问题的解决步骤归纳成可复用的套路。Java 语言里的 23 种设计模式最早是四个人GoFGang of Four在《Design Patterns》这本书里总结出来的。书里把它分成三大类创建型、结构型、行为型。这个分类不是随便分的它对应着你在设计一个系统时最关心的三件事对象怎么创建、类和对象怎么组合、对象之间怎么交互。很多人学设计模式的时候上来就拿着 UML 类图去死磕什么依赖倒置、接口隔离背了一堆概念结果到项目里还是写不出来。问题出在哪出在把模式当成了知识而不是工具。知识是可以背的工具是用会的。所以这篇文章我不会铺开 23 种模式逐个念定义而是把每一种模式背后解决的核心问题、使用场景、容易踩的坑讲明白让你看完之后至少知道项目里遇到这个问题我该用哪个套路为什么。1.2 为什么是 23 种这个数字怎么来的先纠正一个常见的误解23 种设计模式不是 Java 独有的它跟语言无关。GoF 在 1994 年出版的那本书里从大量实际的面向对象系统中提炼出了这 23 种通用模式。之所以叫23 种是因为当时作者们归纳来归纳去发现这几个套路能覆盖绝大多数反复出现的设计问题。这个数字本身不重要重要的是它的分类逻辑——三大类创建型模式5 种单例、工厂方法、抽象工厂、建造者、原型。解决的核心问题都是怎么让对象的创建更灵活、更可控。创建型就是管理对象的诞生过程别让你的代码到处 new。结构型模式7 种适配器、装饰器、代理、外观、桥接、组合、享元。解决的核心问题是怎么把类和对象组织成更大的结构同时保持灵活。行为型模式11 种策略、模板方法、观察者、迭代器、责任链、命令、备忘录、状态、访问者、中介者、解释器。解决的核心问题是对象之间怎么分配职责、怎么通信。你如果仔细看会发现这三大类其实对应的是你写代码时的三个时间节点创建对象时、组织对象时、对象运行时。一个系统从设计到运行逃不开这三件事。所以学设计模式先理解这个分类框架比一个个念名字重要得多。1.3 掌握设计模式的前提面向对象的基本功这句话可能有点扎心但我还是得说设计模式是建立在面向对象三大特性之上的如果你对封装、继承、多态理解不到位学设计模式会非常痛苦。举个例子策略模式看起来很简单就是定义一个接口、多个实现类、然后在运行时选择具体实现。但你要是没吃透多态你很难理解为什么明明可以 if-else 判断非要搞出三五个类来。多态的本质是调用方不关心具体实现只关心接口约定策略模式把它放大到了算法层面。再比如装饰器模式它的核心是组合优于继承。Java 里的 IO 流就是典型例子new BufferedInputStream(new FileInputStream(...))一层套一层靠组合给对象动态增加功能。你如果不理解组合和继承的取舍逻辑看到这种嵌套的写法会觉得莫名其妙。所以如果你觉得设计模式学起来像天书先回头补一下面向对象基础接口和抽象类的区别、多态在运行时怎么起作用、组合和继承各自适合什么场景。基础打牢了设计模式其实就是一层窗户纸。2. 创建型模式对象怎么来往往比对象本身更重要2.1 单例模式一个类的唯一身份证和它的三个坑单例模式应该是 23 种模式里最简单、也是最容易被用烂的一个。它的核心诉求只有一句话某个类在整个系统里只能有一个实例。典型的场景是配置管理器、线程池、数据库连接池、日志对象等这种全局共享的资源如果出现多个实例要么浪费资源要么状态不一致。Java 里写单例的方式有很多种我直接说结论日常开发用枚举单例或者静态内部类单例最稳妥。静态内部类写法是这样的public class ConfigManager { private ConfigManager() {} private static class Holder { private static final ConfigManager INSTANCE new ConfigManager(); } public static ConfigManager getInstance() { return Holder.INSTANCE; } }这种方式既做到了懒加载第一次调用时才创建又利用了 JVM 的类加载机制保证了线程安全还避免了synchronized锁带来的性能开销。单例模式看起来简单但实际用起来坑不少。我踩过最典型的三个坑第一在多线程环境下用双重检查锁DCL时忘了给实例字段加 volatile。Java 指令重排在极端情况下会让其他线程拿到一个半初始化的对象这种 bug 很难查因为它不是每次都出现。第二把单例当成全局变量用。有些同学发现单例方便就什么数据都往里面塞结果单例类变成了一个巨大的状态容器代码耦合度急剧上升。单例的本质是控制实例数量不是提供一个全局存储区。第三序列化和反射会破坏单例。如果你的单例类实现了Serializable反序列化时会重新创建实例反射调用私有构造器也能开辟新实例。当然绝大多数业务系统不会遇到这种极端情况但如果你的代码是给框架用的需要留意这个问题。我个人的态度是单例该用就用但别滥用。一个系统里用到十个以上的单例你就要反思是不是设计有问题——很多情况下用依赖注入容器比如 Spring 的Component它默认也是单例的可以更好地管理对象生命周期没必要自己手写单例。2.2 工厂方法 vs 抽象工厂别再把它们混为一谈工厂模式可能是面试里被问得最多的创建型模式了但也是被误解得最深的。很多人觉得工厂模式就是把 new 的过程封装到一个类里这种理解太皮毛了。先说工厂方法模式。它解决的核心问题是当一段代码需要创建某种对象但又不确定具体创建哪一个子类时把创建对象的决定权交给子类。简单来说父类定义了一个创建对象的接口子类决定实例化谁。想象一个日志框架现在有很多种日志文件日志、控制台日志、数据库日志。你希望框架本身不关心日志类型只负责调用创建日志器的方法而具体的创建逻辑由配置文件决定。这就是工厂方法模式的典型场景。public abstract class LoggerFactory { public abstract Logger createLogger(); public void writeLog(String message) { Logger logger createLogger(); logger.log(message); } }再来说抽象工厂模式。它的核心场景是创建一组相互关联的对象而且这组对象可能有多种产品族。举个好理解的例子UI 组件库里有按钮、输入框、弹窗而不同操作系统的控件风格不一样Windows 风格的按钮和 Linux 风格的按钮观感不同。抽象工厂就是提供一个接口让子类生产配套的一整套产品。如果你只需要创建一种产品用工厂方法。如果你需要创建一组配套的产品而且它们之间存在约束关系比如 Windows 的按钮必须配 Windows 样式的弹窗用抽象工厂。这两个模式最大的区别不是代码形态而是服务对象的粒度。工厂方法服务于一个产品对象抽象工厂服务于一组产品族。很多同学把一堆工厂类写在同一个包里命名都是XxxFactory看起来差不多实际职责完全不同。你写代码之前先想清楚我是要选择一个产品还是要选择一条产品线。2.3 建造者模式与原型模式搭积木和抄作业建造者模式解决的核心问题是一个对象的属性太多构造器参数列表长得不忍直视而且部分参数可选。遇到这种类最简陋的做法是写一堆重载构造器或者开一堆 setter。重载构造器的问题是调用方搞不清楚每个参数的含义setter 的问题是这个对象的中间状态可以被外部修改不可保证完整性。我见过最典型的一个例子是某个用户实体类有十几个字段包括姓名、年龄、邮箱、手机号、地址、爱好集合等等。早期代码里就是new User(张三, 25, xxxxx.com, 138..., 上海, ...)看完这一行你根本不知道第三个参数是邮箱还是地址。后来改造成建造者模式后代码变成User user User.builder() .name(张三) .age(25) .email(xxxxx.com) .city(上海) .build();每个参数都带着名字可读性直接提升一个档次。Java 领域里很流行的 Lombok 注解Builder就是为此设计的。原型模式则完全是另一个思路它不是通过 new 来创建对象而是复制一个已有的对象作为模板。它的核心价值在于当“创建对象的过程非常昂贵”比如需要查数据库、做大量初始计算时直接复制已有对象能大幅提升性能同时它让对象创建不依赖具体的类。Java 里实现原型模式最直接的途径是Cloneable接口但这里有个很大的坑Object.clone()是浅拷贝如果你的类里有引用类型的成员变量复制出来的对象和原对象会共享同一个子对象。一旦你改了副本的嵌套对象原对象也跟着变了这是很多隐蔽 bug 的来源。所以用原型模式时必须明确你需要深拷贝还是浅拷贝深拷贝通常需要自己重写clone()方法或者用序列化、手写拷贝构造器。我个人的看法是现在 JVM 创建对象的成本已经非常低了如果不是高频创建的场景比如每秒几十万次的缓存对象生成原型模式用到的机会并不多。但理解它的存在能让你在看到复制对象这类需求时多一种设计思路。3. 结构型模式把类和对象搭出好看的骨架3.1 适配器、装饰器、代理一字之差用途天壤之别结构型模式里适配器、装饰器、代理是三组非常容易混淆的模式因为它们代码形态上都是包一层。但它们的设计意图完全不同这是深入理解设计模式很关键的一点。先说适配器模式。它的作用是让两个不兼容的接口能够协同工作。比如你有一个老系统里的XmlOrderService只提供 XML 数据。新系统里其他模块都需要 JSON 格式。你不可能重写老系统所以写一个适配器把 XML 转成 JSON 再对接新模块。适配器解决的是接口不匹配的问题核心在于转换。然后是装饰器模式。它的作用是在不修改原有类的情况下动态地给对象添加新功能。最经典的例子就是 Java IOnew BufferedReader(new InputStreamReader(new FileInputStream(file)))一层层包裹每一层都增加了新的能力。装饰器解决的是功能扩展的问题核心在于增量叠加。最后是代理模式。它的作用是控制对对象的访问在访问前或者访问后执行额外的逻辑。分类上又分静态代理和动态代理。Spring AOP 的底层原理就是动态代理。代理和装饰器在代码结构上非常像很多人分不清。区别在于装饰器是功能增强代理是访问控制。你写了一个日志记录的类包住真实对象这既可能是装饰器也可能是代理关键看你的意图。如果你是为了在调用前后记录日志这是代理如果你是为了给对象本身新增功能这是装饰器。这三者放在一起对比一下更直观模式解决的核心问题典型场景关键特征适配器接口不兼容新旧系统对接转换接口装饰器动态增加功能Java IO 流层层包裹、功能叠加代理控制访问权限校验、日志、延迟加载访问前后附加逻辑碰到包一层的代码别急着认定是哪种模式先想清楚背后的意图。3.2 组合模式与桥接模式树形结构和多维变化组合模式解决的核心问题是让部分和整体能用一致的方式处理。最典型的例子是文件系统一个文件夹里面可以有多个子文件夹和文件无论你操作的是一个文件还是整个目录都用同样的接口。Java 的HashMap里的putAll方法就是一个类似思想的体现——你传一个Map进去不管它是小 map 还是嵌套的大 map处理逻辑一致。组合模式的核心结构是树形的叶子节点和分支节点都实现同一个接口分支节点里持有子节点的集合。实际编码中组合模式常用在菜单系统、组织架构、分类目录这种天然有层级关系的场景。你需要记住的关键点是不要在设计模式代码里写出if (node instanceof Leaf)这种判断——那就没体现统一接口的价值了。桥接模式则解决另一个维度的问题当一个类有两个维度的变化时不要用继承去无限膨胀而是把两个维度拆开各自独立扩展再通过组合的方式桥接起来。我用一个通俗的例子说明。假设你要做一个图形绘制系统图形的类型有圆形、方形而画笔的颜色有红色、蓝色。如果你用继承来实现要建圆形红笔、圆形蓝笔、方形红笔、方形蓝笔4 个类。如果再加一个三角形就变成 6 个类。继承体系会急速膨胀。桥接模式的做法是把图形和绘制的颜色拆成两个独立的类层级图形里持有颜色的引用画图形的时候委托颜色去执行绘制。这样以后再增加三角形只需要加一个图形子类不再需要关心颜色有多少种。核心思想就是用组合代替继承破解多维度继承爆炸。这个模式在实际框架里很常见比如 JDBC 的驱动体系JDBC API 是抽象层具体的数据库驱动是另一层两者通过DriverManager桥接起来。理解桥接模式你再看那些既有多维度扩展、又不想搞一堆子类的设计时思路会清晰很多。3.3 外观模式与享元模式门面简化调用细粒度复用外观模式Facade Pattern在我看来是最容易提升代码友好度的模式之一。它解决的核心问题是子系统内部很复杂调用方看不懂也不关心你只要提供一个简单的门面接口把内部的复杂性全部挡住。举个例子你搭建了一个家庭影院系统包含投影仪、音响、灯光、幕布。如果调用方要看电影需要依次开启幕布、开投影仪、开音响、调暗灯光、选择片源。每次都得调用一堆方法调用方直接被细节淹没。有了外观模式你只需要提供一个MovieFacade.watchMovie()方法内部替调用方搞定全部流程。这个思想的本质是接口简化。很多开源框架的入口只有一个类为什么因为它不想让使用方陷入内部实现的泥潭。分层架构里的 Service 层实际上也扮演了门面的角色Controller 不关心你的 Service 内部用了几张表、调了几个外部接口它只需要一个placeOrder()方法。享元模式则是一个偏底层优化的模式。它解决的核心问题是大量细粒度的对象被反复创建造成内存浪费能不能共享这些对象经典的场景是字符串常量池、Integer 缓存-128 到 127、线程池。享元模式的关键是把对象的状态分成两类内部状态可以共享的比如字符本身和外部状态不能共享的比如字符的位置。代码层面享元通常配合工厂来使用工厂维护一个对象池请求过来时先查池子有就直接返回没有再创建。日常业务开发里享元模式用得不多但在写工具库、底层框架、游戏开发时它是一个有效的内存优化手段。你只需要记住当你的系统里出现了大量重复的、不可变的对象时想想能不能把它们共享起来。4. 行为型模式真正考验设计功力的部分4.1 策略模式与模板方法模式算法的横向复用和纵向复用行为型模式里我觉得对日常开发帮助最大的是策略模式。它解决的核心问题一段代码中某个算法有多种实现方式而且可以互相替换调用方不希望和具体算法绑定。最经典的例子是促销折扣一个订单有多种折扣类型满减、打折、无优惠。如果你写 if-else每增加一种折扣就要改那段判断代码。而这个判断逻辑本身就是一种不稳定需求if-else 会让你的代码不断膨胀最终变成彩虹类到处都是条件分支。策略模式的代码套路很简单public interface DiscountStrategy { BigDecimal applyDiscount(BigDecimal amount); } public class FullReductionStrategy implements DiscountStrategy { ... } public class PercentageStrategy implements DiscountStrategy { ... } public class NoDiscountStrategy implements DiscountStrategy { ... }调用方只需要持有DiscountStrategy接口在运行时传入具体实现即可。这个模式的精髓在于它把选择权从调用方内部挪到了外部让算法可以独立扩展。策略模式在框架源码里无处不在比如Comparator接口比如 Spring 里的HandlerMapping。你写业务代码时如果有多个 if-else 分支是在做同一件事的不同做法优先考虑用策略模式代替。模板方法模式和策略模式是互补的。策略模式把算法的不同部分交给外部替换而模板方法模式则是把一个流程的骨架定死把其中某些步骤留给子类去实现。举一个很生活化的例子泡茶和泡咖啡的流程是相似的烧水、放原料、冲泡、加调料。如果把泡饮品做成一个模板方法烧水是固定的步骤放什么原料由子类决定。这就是模板方法模式。模板方法的核心价值在于防止流程被破坏。如果你用策略模式让调用方自己拼装流程调用方可能漏掉某一步而模板方法把流程锁死在父类里子类只能改细节不能改骨架。这在很多框架的扩展点里非常常见。4.2 观察者模式与责任链模式事件驱动的两种经典范式观察者模式解决的核心问题当一个对象的状态发生变化时希望其他一组对象都得到通知但它们之间不应该耦合。最典型的就是 GUI 里的按钮点击事件、订阅发布系统、消息通知。Java 里有个很古老的Observable类和Observer接口不过谁都会用。更现代的做法是直接自己定义一个简单的监听器接口或者用框架的事件机制比如 Spring 的ApplicationEvent。观察者模式的本质是把状态变化作为事件推送给关注者让发布方不依赖具体的订阅方。这个模式有个常见误区观察者之间的关系是广播式的一个事件发出所有订阅者都会收到。如果你需要的不是广播而是有选择地通知且按顺序处理那就要考虑责任链模式。责任链模式解决的核心问题一个请求可能被多个对象处理而且处理者之间的顺序是动态的你要把它们串成一条链请求沿着链传递直到某个结点处理它为止。最典型的例子是审批流请假一天和请假五天走的审批人不一样请假日期越长审批链越长。还有 Java Web 领域的 Filter 过滤器链也是典型的责任链。这两个模式的共同点是事件驱动但区别很明显观察者是一对多的广播责任链是一对一的接力传递每个处理者都有机会处理也可以选择不处理交给下家。4.3 其余行为型模式用一张表和几个例子串起来除了策略、模板方法、观察者、责任链行为型模式还有 7 种我结合自己的经验快速过一遍迭代器模式让你不暴露内部结构就能遍历集合元素。Java 的Iterator就是它的实现。日常开发里你大概率不需要自己写迭代器但理解它能帮你读懂集合源码。命令模式把请求封装成对象从而支持撤销、队列、日志等操作。GUI 里的撤销操作就是命令模式的典型场景。我见过一些任务调度框架也在用命令模式把要执行的操作变成可序列化的对象。备忘录模式保存对象某个时刻的状态以便以后恢复。典型的例子是文档编辑器里的撤销到某个版本。现在的分布式系统通常不这么做而是用事件溯源但备忘录的思维是相通的。状态模式对象的行为取决于它的状态状态改变时行为随之改变。这个模式和策略模式在代码上很像但意图不同策略模式是算法可替换状态模式是状态转换导致行为切换。订单状态流转是它的最佳实践场景。访问者模式在不修改对象结构的前提下给对象增加新的操作。这个模式理解起来稍微抽象实际业务代码里用得不多但很多编译器框架、AST 遍历里很常见。如果你不是在做编译类或语义分析类系统可以先了解它不用强求自己写出来。中介者模式用一个中介对象封装一组对象之间的交互避免对象之间直接互相引用。聊天室、消息队列、系统之间通过中间件通信都是这个思想。解释器模式定义一门语言的文法用它解释句子。正则表达式引擎、SQL 解析器都是它的忠实拥趸。日常业务开发中自己写解释器的场景极少但了解它能让你明白解析类系统是怎么运转的。这 7 种模式我给一个快速索引表模式解决的核心问题日常碰到的概率迭代器遍历集合但不暴露内部结构经常用但不用自己写命令把请求封成对象支持撤销/队列中任务系统常见备忘录保存并恢复状态低但思路重要状态状态转换驱动行为变化高订单/流程类系统访问者不修改结构增加新操作低编译/分析类中介者对象之间不直接互相引用中中间件思想解释器解析并执行文法低框架类系统5. 把设计模式落到项目里选型思路与实战示例5.1 判断要不要用一个模式的三个问题模式用对了是解耦用错了是过度设计。我自己决策时一般会问三个问题第一个问题这里真的有变化吗设计模式是为了应对变化而存在的。如果一段代码在一个系统生命周期内都不太可能变化那用设计模式就是画蛇添足。比如你一个小型工具类内部就一种算法未来也不会加第二种那就老老实实写普通类没必要搞策略模式。我自己见过最离谱的例子是一个只有一个实现的PaymentService接口为了面向接口编程硬生生创建了接口和实现两个文件还配了一个工厂类。这种代码你看着很规范实际上是空转。第二个问题用了模式之后代码真的更简单了吗很多时候两种方案一个代码量更少、但扩展性差一点另一个代码量更多、但扩展性好很多。如果你的系统只有两三个调用场景我倾向于选择前者。设计模式的本质是用结构换灵活性当灵活性没有实际价值时这个交易不划算。第三个问题模式的代价我承担得起吗任何模式都会增加类数量增加阅读成本。一个用了大量模式的项目新人上手会比较痛苦。你需要权衡的是这个项目的生命周期有多长会不会频繁变更团队的整体水平怎样如果你是在一个三年交付一次的银行核心系统里那多用点模式没问题如果你是在一个快速迭代的活动运营系统里简单直接才更高效。5.2 一个实战例子订单状态流转的设计我们来设计一个订单取消流程。这个场景里订单有很多状态待支付、已支付、配送中、已完成、已取消。不同的状态下用户点击取消订单系统执行的行为是不同的待支付状态可以直接取消不需要退款。已支付状态取消后需要走退款流程退款金额还得算上用的优惠券。配送中状态不能直接取消要进入售后审核流程。初学阶段的写法大概率是一个cancel()方法里堆 if-elsepublic void cancel(Order order) { if (order.getStatus() Status.PENDING_PAYMENT) { // 直接置为取消 } else if (order.getStatus() Status.PAID) { // 退款 } else if (order.getStatus() Status.SHIPPING) { // 售后审核 } }这种写法的缺点很明显每加一个状态就要改这里的判断每加一种新动作比如取消后发短信也要改这里。多个状态、多个动作最后这方法会膨胀成一个巨大的分支怪物。用状态模式重新设计把每种状态封装成一个类状态的切换和该状态下能做的操作都由这个类自己决定。订单对象持有一个当前状态对象调用order.cancel()时其实委托给当前状态对象去执行。这样做的直接好处是新增一个状态不用动已有的状态类调整状态流转关系也只需要改对应的状态类。这个模式在订单、审批、工单这类状态繁多的系统里确实能救你一命。如果状态本身不太复杂但取消行为的算法有多种那更适合策略模式。比如同一个状态下普通用户取消、管理员强制取消、超时系统取消几种取消策略各有各的处理逻辑。这两种模式的界限在代码形态上确实接近我在实际工作中也会把状态模式和策略模式组合使用用状态模式管理状态流转用策略模式处理每种状态下可执行的多种算法。5.3 对照框架源码学比对着书背快得多很多人学完设计模式最大的困惑是我知道每个模式长什么样就是不知道实际项目里它长什么样。一个很有效的方法是拿你日常用的框架源码当活教材。比如 Spring 里的BeanFactory它本质上就是一个抽象工厂Spring AOP 的动态代理就是代理模式的教科书Spring MVC 里的HandlerMapping是策略模式ApplicationContext容器启动过程是模板方法模式事件发布机制是观察者模式Filter链和拦截器链是责任链模式。你打开 JDK 源码也是一堆宝藏Collections.sort()依赖Comparable/Comparator这是策略模式HashMap的迭代器是迭代器模式Integer.valueOf()里的缓存是享元模式ByteArrayInputStream和BufferedInputStream的组合是装饰器模式。在框架层面你见到的不是一个个孤立的名词而是一套被验证过的真实用法。我的建议是不是先学完 23 种再去读源码而是遇到一个模式就去源码里找它的实例。这样你会发现原来模式不是设计稿里那几行类图而是活的代码。6. 学习路径、常见误区和面试避坑6.1 不要背模式要背问题我面试过不少候选人设计模式这块的差距非常明显。有些人能一字不差地背诵某个模式的定义但问你这个项目里的权限模块可以用什么模式为什么他就答不上来。有些人可能连模式名字都说不全但会跟你说我在这里搞了个策略因为以后鉴权方式会有多种这种人我是愿意给高分的。学设计模式的正确姿势是倒过来学先看问题再看解法。比如你看到了一个多个处理者依次尝试处理请求的流程你意识到这是一个链条然后才知道有个名字叫责任链模式。如果你先背了责任链的概念再去找场景会显得很生硬。我自己学习的时候很喜欢用意图对比的方式。比如上面提到的适配器、装饰器、代理它们代码结构都很像但有各自的意图。我会画一张表把它们的意图列出来反复对比。这是理解设计模式最扎实的方法搞清楚这个模式为什么存在它想解决什么矛盾用了什么代价换取什么收益。6.2 常见错误的使用场景我在代码评审时见过不少为用模式而用模式的情况。这里帮大家排一排雷第一个雷给一成不变的流程硬套模板方法。有些同学把一个只有唯一实现的流程拆成抽象方法挂到父类里子类只有一两个还要重写抽象方法。这不是模板方法这是给代码搭架子。模板方法适用的是骨架稳定、部分步骤需要被子类定制的场景如果根本没有多个子类就别为了模式而抽象。第二个雷滥用单例导致全局状态混乱。有些代码把可变的状态放进单例里两个线程同时改同一个字段没有任何同步措施。这就是设计原则没搞对单例应该倾向于保存无状态的服务类而不是有状态的数据类。第三个雷过度使用装饰器让调用方看不懂对象被包了多少层。装饰器虽然灵活但每一层的对象都是不同包装调试时输出会很痛苦。我见过一个写的异常复杂的日志方案一个Logger被先后包装了格式化、过滤、缓冲、路由四层新人看到那几行嵌套构造器直接懵了。装饰器可以用但要控制层数或者在工厂里封装好让调用方只通过工厂获取最终对象。第四个雷在不该用观察者的地方硬用观察者导致幽灵事件难以追踪。事件机制虽然解耦但也带来了隐式控制流——代码里看不到直接的调用关系出 bug 时很难追踪。如果你的系统里事件发出去没有明确消费者或者消费者很多且不可控建议先想想是不是直接调用更清晰。解耦不是目的可维护才是目的。6.3 推荐的学习路径和实操方法如果你是想系统学透设计模式我建议按下面的路径走第一阶段建立分类框架。先记住 23 种模式分哪三类每类解决什么问题。不用深入每个细节目标是看到模式名字时能说出它属于哪类、核心意图是什么。第二阶段逐个精读核心模式。挑选场景最常用的模式至少精读这几个单例、工厂含抽象工厂、建造者、代理、装饰器、适配器、策略、观察者、模板方法、状态、责任链。每个模式都配套做一个小 demo用代码验证你的理解并对照源码找实例。第三阶段回看自己的项目代码。把你之前写的业务代码里if-else 密集的函数、类爆炸的地方标记出来试着用学过的一两个模式重构一下。注意重点不是重构完代码变好了而是我能不能识别出这里适用什么模式。这个识别能力才是面试和真实开发中最稀缺的。第四阶段读框架源码。Spring、MyBatis、Netty随便挑一个你常用的框架从启动入口开始顺着读遇到熟悉的模式就停下来标注。这一步能帮你把模式从纸面上的类图变成现实中可触摸的代码。有一些朋友会问我要不要学 UML 类图。我的建议是类图不用画得多漂亮但一定要会看。模式和 UML 的核心关系是模式文档通常用类图表达静态结构用时序图表达动态交互。你在理解模式时脑子里至少要能想象出谁持有了谁、谁调用了谁。我会建议你用简单的类图符来梳理一个方框是一个类箭头代表依赖或继承先会画这个就够用了不用纠结那些花哨的符号。7. 最后分享一点我的个人体会从我自己的经历来说真正搞懂设计模式标志不是我能默写 23 个模式或者面试时能说出类图的每个符号而是你在写代码的时候脑子里自动浮现出一种气味判断——看到一堆 if-else 时你意识到这里有变化点看到两个不匹配的接口对接时你想到适配器看到一个对象需要被多个逻辑同时观察时你想到事件机制。这种能力不是看书看出来的一定是靠调试、踩坑、重构积累出来的。我学设计模式的时候也走过弯路有一段时间恨不得把每个类都套个接口每个业务流程都拆成策略模板观察者结果代码复杂度飙升同事看我的代码都想骂人。后来才慢慢明白设计模式的核心是应对确实存在的变化不是让代码看起来高大上。所以你读完这篇文章我不希望你去背模式清单而是希望你记住三个词意图、变化、权衡。每个模式都是一种权衡交易没有银弹。等你把三大类、23 种模式的意图搞清楚并且在真实代码里验证过它们的使用场景后你会发现自己看代码的视角完全不一样了——你看到的不再是一行行语句而是一个个结构以及它们之间的通信方式。这不是一遍就能吃透的事但值得花时间。
阅读完成 · 觉得有帮助?
咨询建站