设计模式慢功夫:结构型与行为型模式的取舍与重构实践

发布时间:2026/10/31 9:12:05
设计模式慢功夫:结构型与行为型模式的取舍与重构实践 1. 为什么“从前慢”这三个字恰好点中了设计模式的学习命门“从前慢”这个前缀放在设计模式系列的第二篇上我第一反应不是文艺而是精准。设计模式这东西学第一遍的时候几乎所有人都会经历一个错觉看懂了。单例、工厂、建造者每个模式翻过去都觉得“就这”代码也就二三十行结构图一目了然。可真到了项目里面对一个已经写了三千行的业务类让你判断这里该不该抽一个策略出来绝大多数人是懵的。问题不在于模式本身难而在于模式是慢功夫它需要你在脑子里反复咀嚼“什么场景下不这么写会痛”而不是背下“策略模式定义了一系列算法”。这一篇作为系列的第二篇章我理解它的定位是承上启下第一篇大概率把创建型模式的地基打了第二篇要往结构型和行为型的深水区走同时把“为什么慢”这件事讲透。我打算按自己带人、做代码评审、重构老系统的实际经验来拆不搞教科书式的定义罗列。适合谁看如果你已经能写 Java、能看懂接口和抽象类但每次写业务代码都是 if-else 一路平推或者你接手过一个“改一处崩三处”的老项目那这篇就是给你准备的。核心关键词我会反复落在设计模式、解耦、可扩展性、重构、开闭原则这几个点上因为它们才是模式存在的真正理由。先说一个我自己的判断设计模式最大的价值不是“让代码更优雅”而是让未来的修改成本可控。优雅是结果不是目的。很多人学模式学偏了就是奔着优雅去的结果在只有两个分支的地方硬套一个策略模式多出四个类维护的人骂娘。所以这篇我会花大量篇幅讲“什么时候不该用”这比讲“怎么用”重要得多。慢下来先想清楚变化的边界在哪再决定动不动刀这才是“从前慢”真正想传达的东西。2. 结构型模式在“组合”与“继承”之间做一次清醒的取舍2.1 继承是快刀但刀口容易卷刃刚入行的前两年我特别爱用继承。父类抽一个模板方法子类重写几个钩子感觉代码复用率拉满。直到有一次做一个订单导出功能需求是导出 Excel、CSV、PDF 三种格式每种格式又要支持“全量”和“按条件筛选”两种模式。我当时的写法是抽象一个BaseExporter然后ExcelExporter、CsvExporter、PdfExporter各继承一份筛选逻辑再各自复制一遍。六个类看起来挺整齐。结果第三周产品说PDF 要加个水印Excel 不要CSV 的筛选条件要跟另外两个不一样。我改BaseExporter怕影响全部改子类又要复制粘贴。这就是继承的典型陷阱——继承是编译期绑定的强耦合父类一抖动所有子类跟着震。结构型模式里的桥接模式就是专门治这个病的把“导出格式”和“筛选维度”拆成两个独立的继承体系用组合把它们桥接起来。格式那边三个类筛选那边两个类总共五个类但任意组合都不需要新增类加一种格式只动格式体系加一种筛选只动筛选体系。这里的关键认知是继承表达的是“is-a”组合表达的是“has-a”而业务里绝大多数变化其实是“has-a”维度的变化。导出器“拥有”一种筛选策略而不是“是一种”筛选策略。想通这一层桥接模式就不需要背了它是自然推导出来的。2.2 装饰器与代理长得像用错了就是灾难这两个模式在代码结构上几乎一模一样都是持有一个同类型的引用都在调用前后做点手脚。我见过太多人把它们混用结果埋下大坑。给你一个最直白的区分标准装饰器是“叠加能力”代理是“控制访问”。举个生活化的例子。装饰器像给一杯咖啡加奶、加糖、加肉桂每一层都是在你原有饮品上增加新特性加几层都行顺序还能换。代理则像公司前台你要见老板必须先过前台前台决定让不让你进、要不要登记、要不要转接。前台不改变老板的能力它控制的是“你能不能接触到老板”。落到代码上Java 的InputStream体系是装饰器的教科书案例BufferedInputStream包FileInputStream加的是缓冲能力你还可以再包一层DataInputStream加类型读取能力。而代理的典型场景是远程调用、权限校验、懒加载——比如一个图片查看器真实图片对象很大先用代理占位真正要显示时才去加载。我踩过的一个坑是在一个需要记录方法耗时的场景里我用装饰器包了三层结果每层都记了一次耗时日志里同一个方法出现三条记录排查了半天。装饰器会叠加代理通常只该有一层拦截这个区别在写日志、埋点、事务这类横切逻辑时尤其要命。2.3 外观模式给混乱的子系统留一扇干净的门老系统重构时外观模式是我用得最频繁的一个没有之一。原因很现实你不可能一次性把一坨乱麻理顺但你可以先给它套一个门面让新代码不再直接依赖那坨乱麻。具体做法是新建一个XxxFacade类把子系统里七八个类的调用顺序、参数组装、异常处理全部收口到门面里外部只调门面的一两个方法。这么做的好处是把“脏”隔离在一个可控的边界内。门面内部可以继续乱但至少乱的范围不再扩散。等哪天有精力了只改门面内部实现外部调用方一行不用动。我做过一个支付对账的模块底层涉及账单拉取、格式解析、差异比对、结果落库四个子系统每个子系统又有自己的初始化顺序。我加了一个ReconcileFacade对外只暴露reconcile(date)一个方法新来的同事再也不用去研究那四个子系统怎么串的。这里有个实操心得门面方法不要设计得太“万能”我见过有人把门面做成一个巨型参数对象什么都能传结果门面内部又是一堆 if-else等于把混乱往上挪了一层白干。门面应该是“窄接口”一个方法对应一个明确的业务动作。3. 行为型模式把散落各处的 if-else 收编成可管理的结构3.1 策略模式不是消灭 if-else而是给变化一个安放之处关于策略模式网上最多的说法是“用策略模式替代 if-else”这话对一半。我明确告诉你两个分支的 if-else 用策略模式是过度设计五个以上且未来还会增加的分支才值得考虑。判断标准不是分支数量而是“这个分支集合会不会变、变的频率高不高、每个分支的逻辑重不重”。我做过一个优惠券核销的功能最初是 if-else 判断券类型满减、折扣、立减、兑换。四个分支每个分支逻辑就三五行的算术我忍住了没抽策略。后来业务加了“阶梯满减”“限品类折扣”“新客专享”分支涨到七个每个分支开始涉及库存校验、叠加规则、互斥判断单个分支代码涨到七八十行。这时候再 if-else一个方法四百多行改一个分支要滚半天鼠标。这时候抽策略才是对的。抽的时候有个细节很多人做错策略的入参和出参必须统一。我见过有人每个策略类的calculate方法签名都不一样调用方还得先判断类型再强转等于把 if-else 挪到了调用方毫无意义。正确做法是定义一个DiscountStrategy接口apply(OrderContext ctx)返回DiscountResult所有实现类严格对齐。然后配一个简单的工厂或者枚举映射根据券类型拿到对应策略。这里可以用一个MapString, DiscountStrategy在启动时注册好比反射优雅得多也更好调试。提示策略模式最常见的误用是“策略类里又写了 if-else 判断上下文”。如果你的策略实现内部还在根据订单金额分情况那说明策略的粒度切错了应该继续往下拆而不是在一个策略里再套分支。3.2 责任链模式让审批流、过滤器这类场景不再层层嵌套责任链是我认为最被低估的一个模式。很多人只在学 Servlet Filter 的时候见过它觉得离业务很远。实际上但凡涉及“多个处理者按顺序尝试处理谁处理得了谁处理或者每个都处理一遍”的场景责任链都是首选。我做过一个内容审核的链路先过敏感词再过图片识别再过人工抽检标记最后落审核结果。如果用传统写法就是三层嵌套的 if每层还要处理“上一环节已拒绝则跳过”的逻辑代码像俄罗斯套娃。用责任链之后每个审核环节是一个Handler持有next引用handle方法里要么处理完调next要么直接中断返回。主流程只有一行chain.handle(context)清爽得不像话。实现责任链有两个关键决策要想清楚。第一是中断语义是“有一个处理成功就停”还是“每个都要过一遍”前者像审批流经理批了就不用总监批后者像过滤器每个都要执行。这两种语义对应不同的handle写法别混。第二是链的组装方式可以硬编码a.setNext(b).setNext(c)也可以用配置或者注解扫描自动装配。我倾向后者因为审核环节经常增删硬编码每次都要改主流程。用注解标记顺序、启动时扫描排序组装加一个环节只需要新增一个类加个注解符合开闭原则。3.3 观察者模式解耦“事件发生”和“后续动作”的利器观察者模式的价值在于时间解耦和空间解耦事件发生的代码不需要知道谁会响应响应者也不需要知道事件从哪来。这在订单创建这种“一件事触发一堆后续动作”的场景里特别好用。下单成功后要发短信、加积分、更新库存、推消息如果全写在createOrder方法里这个方法会变成一个什么都干的上帝方法而且每加一个后续动作就要改它一次。用观察者之后createOrder只负责发布一个OrderCreatedEvent短信服务、积分服务各自订阅各干各的。加新动作不动下单主流程这是实打实的收益。但这里有个我必须提醒的坑同步观察者会拖慢主流程。如果短信服务响应慢下单接口就跟着慢。所以生产环境里观察者列表里但凡有 IO 操作基本都要配合异步线程池或者消息队列。我早期图省事用同步结果一次短信网关抖动下单接口 P99 直接飙到三秒被用户投诉。后来改成异步发布主流程只负责把事件丢进队列响应时间立刻回到几十毫秒。另一个细节是异常隔离。一个观察者抛异常不能影响其他观察者执行更不能影响主流程。所以发布事件的循环里必须 try-catch 包住每个观察者的调用记录日志后继续。这个点看起来小但线上出过一次积分服务报错导致整个下单回滚的事故就是没做隔离。4. 从“知道模式”到“用对模式”一套可复用的判断流程4.1 先识别变化点再谈模式选型我评审代码时最常问的一个问题是“这段逻辑里哪个部分未来最可能变”答不上来的人往往就是模式乱用的人。设计模式的本质是封装变化如果你连变化在哪都不知道套模式就是瞎套。所以我的实操流程是拿到一段待重构的代码先用注释标出“稳定的部分”和“易变的部分”稳定部分保持直白易变部分才考虑抽象。举个具体例子。一个计算运费的函数里面包含“基础运费计算”稳定按重量乘单价和“会员折扣规则”易变经常调整。那正确的做法是把折扣规则抽成策略基础计算留在原地。而不是把整个运费计算都抽成一个大策略那样每次改折扣都要动整个策略类反而更麻烦。抽象要沿着变化的边界切切多了切少了都是错。4.2 模式选型对照表什么症状吃什么药下面这张表是我自己整理常用的把“代码症状”和“对应模式”直接挂钩比背定义好用得多。注意这只是起点具体还要结合项目约束判断。代码症状优先考虑的模式判断理由慎用情形大量 if-else 分支且分支逻辑较重策略模式分支可独立演化便于单测分支少于三个且逻辑简单多层嵌套的“处理-判断-再处理”责任链模式处理环节可增删、可重排环节固定且极少变化一个对象状态变化引发多方联动观察者模式发布订阅解耦符合开闭响应方有强顺序依赖同一功能需要多种维度自由组合桥接模式避免类爆炸组合优于继承维度只有一个且稳定需要在不改原类的前提下增强功能装饰器模式动态叠加运行期灵活增强逻辑固定且唯一需要控制对某对象的访问代理模式访问控制与业务分离无访问控制需求时属多余子系统调用复杂、顺序敏感外观模式收口复杂度隔离脏代码子系统本身已足够简单这张表我建议打印出来贴在工位上每次想抽模式之前先对一眼。对不上症状就别抽这是防止过度设计最有效的一道闸。4.3 一个完整的重构实录从 600 行上帝类到四个协作对象说个我实际做过的重构。一个促销计算类六百多行方法里套方法核心是一个calculatePrice方法里面处理了会员等级、活动类型、商品品类、时间窗口四个维度的组合判断if-else 嵌套了四层。需求方每次改活动规则开发都要花半天理解这段代码改完还经常出回归 bug。我的重构分三步走。第一步先加测试。这一步不能省没有测试的重构等于赌博。我针对现有逻辑写了二十多个用例覆盖各种组合确保重构前后输出一致。第二步识别维度。四个维度其实是正交的会员等级决定基础折扣活动类型决定额外减免品类决定是否参与时间窗口决定是否生效。这天然适合桥接加策略的组合会员折扣一个策略族活动减免一个策略族品类和时间做成前置校验的责任链。第三步逐步替换。我没有一次性推倒重写而是先抽出一个维度跑测试绿了再抽下一个每步都可回滚。重构完的结果是主类从六百行降到八十行四个维度各自独立成类新增一个活动类型只需要加一个策略实现加一行注册测试用例全部复用。最直观的收益是改需求的时间从半天降到半小时而且心里有底。这里的心得是重构的节奏比重构的方案更重要小步快跑、每步验证比一次性设计一个完美架构靠谱得多。5. 实操中反复踩到的坑与排查清单5.1 模式用错比不用更痛三个真实翻车现场第一个翻车现场是单例滥用。早期我图方便把配置管理、缓存客户端、线程池全做成单例结果单元测试互相污染一个测试改了单例状态另一个测试就挂。后来学乖了单例只用于真正全局唯一且无状态的东西有状态的依赖一律走注入。第二个是工厂模式套娃工厂里再套工厂最后为了创建一个对象要追五层调用栈调试时想死的心都有。工厂的职责应该单一创建逻辑复杂就拆到建造者别在一个工厂里堆。第三个是观察者顺序依赖两个观察者之间有隐式先后要求但代码里没体现某次线程池调度顺序变了就出 bug。凡是观察者之间有依赖就该合并成一个观察者或者显式编排别指望发布顺序。5.2 常见问题速查表现象可能原因排查动作处理建议加了策略后调用方还要判断类型策略接口不统一检查各实现类方法签名统一入参出参用映射表取策略责任链某环节不执行上一环节未调 next 或提前 return在每个 handle 入口打日志明确中断语义补日志观察者导致主流程变慢同步执行 IO 操作看主流程耗时与观察者耗时占比改异步加异常隔离装饰器日志重复记录多层包装重复埋点数日志条数与包装层数埋点只放最外层或改代理门面类越来越臃肿门面承担了业务逻辑看门面方法是否超过百行门面只做编排逻辑下沉重构后行为不一致边界条件遗漏对比重构前后测试用例补边界用例小步回滚5.3 几条不带感情色彩的经验第一先写测试再重构这条没有例外。我见过太多人自信满满直接改结果线上出问题回滚都回不干净。第二一个类只引入一种模式一个类里又策略又责任链又观察者读的人会疯该拆就拆成协作对象。第三模式名不要写进类名OrderStrategy这种命名除了暴露实现细节没有任何好处叫OrderDiscountCalculator更贴近业务。第四注释写“为什么这么设计”而不是“这是什么模式”半年后接手的人不关心你用了什么模式只关心为什么不能简单改。第五能不用模式就不用直白的代码永远是最好的代码模式是给复杂度准备的药没病别吃药。6. 慢下来之后我真正想说的写这个第二篇章的过程中我反复想起自己刚学设计模式那会儿的状态恨不得每个类都套一个模式觉得这样才显得专业。后来被现实教育了几年才明白设计模式是给“已经痛了”的代码准备的不是给“可能会痛”的代码准备的。提前抽象和过度设计本质上是用确定的复杂度去赌不确定的未来赌输的概率还不低。“从前慢”这个系列名我现在的理解是慢在判断快在动手。花时间想清楚变化的边界、想清楚这次抽象值不值、想清楚测试够不够这些想明白了动手改反而快。反过来上来就套模式改完发现方向错了返工的时间够你想十遍。我个人的习惯是每次想抽模式之前先问自己三个问题这个变化半年内会发生几次如果不抽最坏的情况是什么抽了之后新同事要花多久理解三个问题答完该不该抽基本就有答案了。最后分享一个我最近在用的笨办法在项目里建一个design-notes目录每次做了一次模式相关的重构就写一页纸记录“当时什么症状、为什么选这个模式、放弃了哪些方案、后续观察点是什么”。半年后回头看哪些抽对了、哪些抽早了一目了然。这个习惯比读十本模式书都管用因为它逼你把模式和真实场景绑在一起思考。慢一点记录一点下次判断就准一点这大概就是“从前慢”最实在的落地方式。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询