设计模式不是23个固定招式,而是应对变化的思维方式 设计模式这个话题几乎每个写代码的人都绕不开。但奇怪的是大部分介绍它的教程都写得像字典列出23个模式的定义、UML图、优缺点看完记不住用起来更懵。我自己刚入行那几年也踩过这个坑把《设计模式》那本书翻了三四遍还是不知道什么时候该用什么时候不该用。直到后来在真实项目里重构了无数次老代码才慢慢想明白一件事设计模式的核心不是那23个固定招式而是应对变化的思维方式。这篇总结写的是我这几年在实际开发里对设计模式的真实理解包括怎么拆解三类模式、怎么选型、怎么避免过度设计也会给出一些可以直接落地的学习顺序和工程实践建议。不管是刚接触设计模式的新人还是写了两三年代码想提升设计能力的开发者这篇内容都值得花几分钟读完——毕竟设计模式这东西自己踩坑的代价远比提前搞明白要高得多。1. 设计模式到底是什么不是九阳神功而是套路1.1 从历史说清楚23个模式怎么来的90年代有四位大佬合写了一本书名字大概叫《设计模式可复用面向对象软件的基础》书中收录了23种经验总结出的常见设计问题解决方案。这些方案不是某个人关在屋子里凭空想出来的而是从大量真实项目里提炼出的套路。就像老中医抓药同一个病症不同大夫开出来的方子可能不同但有效的那几味药总是反复出现——设计模式做的就是这件事把有效的方子给记下来。23个模式并非法则也非规范。它们是解题思路的合集。同样的需求100个人来写可能有100种写法但从抽象的层面看往往能归入同一个套路。设计模式的价值就是让团队之间能用一种共通的语言来描述方案。A同事说这个模块我用了一个观察者模式其他同事不用看代码就能大致猜到数据是怎么流转的——这是模式最朴素也最有用的价值。这套理论并不复杂它的根本逻辑就是面向对象设计的几个基本原则开闭原则对扩展开放对修改关闭、单一职责原则一个类只管一件事、依赖倒置原则依赖抽象不依赖具体。万变不离其宗理解了这些原则再去学23种模式会轻松特别多。1.2 为什么要学设计模式谈重构、谈阅读源码、谈面试学设计模式没法让你一个月涨薪也没法让你直接从初级变高级。它解决的是代码能跑到代码好改之间那段路的距离问题。举个实际场景。某次给一个视频播放器项目加新功能允许用户切换不同清晰度最后发现如果当初使用策略模式这段需求就是新加一个策略类的事情也就十几分钟搞定。而当时那套代码用了五六个if-else判断叠加改了五十多行测试改了二十多分钟还差点把另一条逻辑带崩。对比下来你就能真切体会到模式的作用——它是给未来的修改留后门的技术。阅读源码时体会更明显。好项目里不会出现一眼望不到头的大函数也不会出现一个类几千行的上帝对象。里面的类通常不大类之间通过接口、事件、容器、委托等机制耦合这些机制就是某种设计模式的具体应用。你不认识模式看源码就只看到一堆散落的文件不知道它为什么要拆成这样你认识模式立刻就能理解作者的设计意图阅读效率成倍提升。面试就不用说了。所有中级以上技术面几乎都会涉及设计模式尤其是策略、工厂、单例、观察者这几个。这不完全是应试需要。面试官真正想了解的是候选人的抽象能力和工程意识——遇到需求变化时是直接改掉原有代码还是空出扩展点。模式就是这种能力最直接的体现。1.3 用一句大白话记住三分类23个模式被分成三类记起来特别简单创建型怎么建对象单例、工厂方法、抽象工厂、建造者、原型。结构型对象怎么组合适配器、装饰器、代理、外观、桥接、组合、享元。行为型对象之间怎么协作观察者、策略、模板方法、迭代器、状态、命令、责任链、中介者、备忘录、访问者、解释器等。用大白话说创建型管的是对象怎么来的结构型管的是对象之间怎么搭配行为型管的是对象之间怎么发消息。这三类各有各的侧重点学的时候能分清类别就等于搭好了骨架后面填充细节会快很多。2. 创建型模式两招解决对象怎么来创建型模式里最常用到的我体感是单例和工厂这两个建造者模式在参数特别多的时候也非常有用。先逐个说清楚它们解决的问题再聊怎么选。2.1 单例全局只有一个实例单例模式保证一个类只有一个实例并提供一个全局访问点。最适合的场景是那些全局共享、创建成本高、状态需要统一管理的对象比如配置管理器、数据库连接池、日志组件。这些对象你要是new出多个来要么浪费资源要么状态不一致带来诡异bug。写单例最需要注意的不是怎么写而是怎么避免线程安全问题。以Python为例经典写法是模块级单例——模块导入后天然只会执行一次这是Python里最干净的单例方式。在Java里则推荐用枚举实现或者用双重检查锁定加volatile。# Python 模块级单例 # logger.py class Logger: def __init__(self): self.log_file app.log logger Logger()# 使用 from logger import logger logger.log_file new.log # 拿到的永远是同一个实例注意单例模式尽量不要滥用。全局唯一确实方便但也意味着隐藏的全局状态——所有地方都往这一个实例里塞数据会让问题极难排查。能用依赖注入传对象就别急着写单例。2.2 工厂把怎么创建和怎么使用拆开工厂模式解决的是创建对象时不想关心具体类的问题。真实项目里最典型的就是支付对接微信、支付宝、银联每种支付方式一个类都继承自同一个支付接口。如果不做工厂业务代码里就得写if-else来判断当前是哪种支付方式然后new对应的类。加一个新支付渠道就要动老代码——这违反开闭原则。工厂模式把创建逻辑集中到一个地方业务代码只依赖抽象接口不依赖具体类。加渠道时新增一个支付类加一个case就完了老代码不需要动。class PaymentFactory: staticmethod def create(channel): if channel wechat: return WechatPay() elif channel alipay: return AlipayPay() elif channel unionpay: return UnionPay() else: raise ValueError(f未知支付渠道: {channel}) payment PaymentFactory.create(channel) payment.pay(order_id, amount)工厂的变体有很多简单工厂、工厂方法、抽象工厂。实际项目中不必太纠结名称核心思路都一样——把创建对象的职责从使用方分离出去让使用方面向接口编程。碰到多种类型可替换的需求就用工厂思路来解准没错。2.3 建造者参数太多时的救星当构造对象需要十几个参数其中还有不少是可选参数时直接构造函数看起来会非常痛苦。一项建筑我们会用建造者模式通过链式调用传参参数可读性好顺序也不敏感还方便设置默认值。class HttpRequestBuilder: def __init__(self): self.url self.method GET self.headers {} self.body None self.timeout 3 def set_url(self, url): self.url url return self def set_method(self, method): self.method method return self def set_headers(self, headers): self.headers headers return self def build(self): return HttpRequest(self) request HttpRequestBuilder().set_url(/api/check).set_method(POST).set_headers({Content-Type: application/json}).build()建造者模式在配置文件加载、HTTP请求构造、复杂报表生成这些场景里非常实用。更重要的是它让代码的可读性极大提升——尤其是新人接手时一眼就能看出每个参数在做什么不用跑去翻构造函数。3. 结构型模式理清对象之间的搭配关系结构型模式关注的是如何组合类和对象形成更大的结构。我最常用到的是装饰器、代理、适配器这三个它们解决三种不同问题但都跟接口与包装有关。3.1 适配器解决接口不匹配适配器模式最好理解类比就是你这个设备是Type-C接口给你一个转接头就能插到USB口上。代码里的适配器干的事就是把已有类的接口转换成客户端期望的接口。一个经典场景是引入第三方SDK。某团队曾经接一个物流轨迹查询服务对方的接口返回字段和本地项目已有的数据结构完全不同。直接把第三方SDK的行为散落在业务代码里会导致所有调用点都需要修改。通过适配器模式把第三方SDK包装成本地统一的接口业务代码只依赖本地接口底层换任何供应商都不影响上层业务。适配器模式的核心价值在于解耦。它把不可控的外部变化挡在系统外面属于保护层。3.2 装饰器动态附加责任装饰器模式是在不改变类结构的前提下给现有对象动态添加职责。我理解它就是剥洋葱一块基础代码做核心事情外面套一层层的包装每层都加一点新功能最终调用时从外到内逐层执行。最直观的例子是Python的装饰器语法def log_call(func): def wrapper(*args, **kwargs): print(f调用函数: {func.__name__}) return func(*args, **kwargs) return wrapper log_call def calculate_discount(amount): return amount * 0.8在业务系统里用装饰器常见场景包括接口调用时统一记录日志、统计耗时、做缓存、做重试这些横切逻辑如果塞进每一个业务方法里代码会爆炸。把它们写成装饰器挂在方法上既保留了核心业务逻辑的纯粹又能按需给不同方法附加不同能力。和继承相比装饰器组合起来更灵活。继承是写死在类上的装饰器是运行时动态组合的。十个功能排列组合出的需求类型数用继承得创建几个类用装饰器随时组合即可。3.3 代理控制对象访问代理模式和装饰器从结构上看很像都是包一层但意图完全不同。装饰器是增强功能代理是控制访问。比如我们访问某个远程服务时用代理来做权限校验、接口缓存、延迟加载、调用拦截等。在合适的写一层保护逻辑在到达真实对象前把关。最经典的应用就是各类框架里的动态代理。比如一个支付模块在真实支付前检查用户是否登录、风控是否通过、金额是否超限——正常的做法不是把校验逻辑塞进支付方法而是通过代理层拦截调用预检查通过后才转发给真实支付对象。class PaymentProxy: def __init__(self, real_payment): self._real_payment real_payment def pay(self, order_id, amount): self._check_permission() self._check_risk(order_id, amount) return self._real_payment.pay(order_id, amount)代理与装饰器的一个实用区分标准是装饰器通常不改变原有调用接口、只是为了增强代理则可能控制到底要不要调用真实对象。存在权限校验拦截被拒绝时代理可以不调用真实对象装饰器一般不做这类拦截。3.4 三个包装型模式怎么区分真实项目里这三个模式经常被混着用面试也常被问区别。简单用一张表概括使用场景差异模式解决的问题经典场景关键动作适配器接口不匹配接入第三方SDK转接口装饰器动态扩展功能日志、缓存、耗时统计增强代理控制访问权限校验、延迟加载拦截用生活场景类比适配器是买了个转换插头目的只是让接口能对上装饰器是给手机买了手机壳功能变多了防摔但手机本身没变代理是给重要人物配了个秘书外人想见本人得先过秘书这关。4. 行为型模式对象之间的协作逻辑行为型模式是所有类别量最大的但实际高频使用的其实也就是几个。4.1 策略把算法逐步抽象出来策略模式解决的问题是同一件事不同情况下做法不同。典型的场景是商品促销计算有打折、满减、无优惠三种策略如果写成if-else每加一种促销规则就需要改代码很容易改出问题。策略模式的做法是把每种规则做成一个策略类业务运行时根据需要选择策略。class DiscountStrategy: def calc(self, amount): raise NotImplementedError class NoneDiscount(DiscountStrategy): def calc(self, amount): return amount class PercentDiscount(DiscountStrategy): def __init__(self, percent): self.percent percent def calc(self, amount): return amount * self.percent class FullReductionDiscount(DiscountStrategy): def __init__(self, full, reduction): self.full, self.reduction full, reduction def calc(self, amount): return amount - (self.reduction if amount self.full else 0)策略模式的关键好处是算法族之间可以互相替换且增加新算法不用动旧代码。这也是所有行为型模式里工程收益最明显的一个——因为业务规则变化几乎天天有if-else膨胀是结构性问题策略模式直接化解它。4.2 观察者让事件主动找上来观察者模式解决的是一方变化多方需要知晓的问题。最典型的应用就是事件驱动架构比如订单状态发生变化后需要同时通知库存模块减库存、短信模块发消息、日志模块记流水。如果这三件事在订单模块里直接全写了订单模块就需要知道所有下游模块的细节耦合度极高。观察者模式让订单模块只管发布状态变化事件订阅者自行处理自己该做的事。class OrderStatusPublisher: def __init__(self): self._subscribers [] def subscribe(self, subscriber): self._subscribers.append(subscriber) def notify(self, order_id, status): for sub in self._subscribers: sub.update(order_id, status)观察者模式最大的实际价值是扩展不用改上游。新增一个数据统计模块想接收订单事件只要在启动时subscribe进来即可订单模块的代码一行都不用改。这也是大型系统里消息中间件的常用设计原理——生产者消费者解耦本质上就是观察者思想的分布式延伸。4.3 模板方法骨架固定细节填充模板方法模式如下父类把算法的骨架流程定好其中某些步骤是抽象的子类通过实现这些抽象步骤来提供细节。最常见的例子是数据迁移或者报表导出流程固定是查询数据→格式化数据→写入文件但具体怎么查、怎么格式化不同模块各不相同。class DataExporter: def export(self): data self._load_data() formatted self._format(data) self._write(formatted) def _load_data(self): raise NotImplementedError def _format(self, data): raise NotImplementedError def _write(self, formatted): raise NotImplementedError模板方法强调的是复用。公共流程写在父类里子类不用重复实现这些流程逻辑不同的细节各自覆盖互不影响。但模板方法有一个常见坑骨架里不要放太多钩子。如果父类里有七八个抽象方法要子类实现子类还是得付出不少代价。使用模板方法时保持骨架精简、抽象方法数量可控。4.4 策略和观察者的选择对比这两个模式看起来没关系但在某些场景下可能会被一起考虑——比如订单状态变化到底是用策略类去处理还是发事件通知一个实战盘点表可能有帮助模式适用场景本质策略算法类变化比如计算规则变化行为上可替换观察者状态变化需要触发多个下游动作状态上可通知如果只有订单金额计算方式不同用策略如果订单状态变化后一堆东西都要被触发用观察者。这两者不但不冲突还经常配合使用——观察者收到订单变化事件后内部再借助策略选择处理方式。5. 模式之间怎么选从一个业务演进看选型直觉模式学多了最痛苦的就是不知道用哪个甚至干脆全部用上结果设计过度。这里分享一个我实践下来的判断方法从需求变化点出发倒推模式选择方式。5.1 从需求变化看选型逻辑设计模式本质上是为变化准备的预案。那么问题来了哪个方向最容易变化我们用一个接入三方支付的系统来举例它已经做了多年演进最初需求只支持微信支付。假如现在公司要加支付宝你的代码改了什么就要看之前有没有做好设计如果代码里到处是if channel wechat写死逻辑那么加支付宝就要搜索所有判断点逐个改——合理做法用工厂把创建逻辑收拢再进一步用策略把支付规则抽出来。如果在支付前后需要加日志和风控校验但不想污染核心支付类——合理做法用装饰器或代理包一层。如果系统要接一个完全不同的三方支付SDK它的接口参数跟本地定义完全不同——合理做法用适配器转换。把这个例子重新看一遍你会发现选模式其实用不了3秒找变化点选对应模式。变化点越清晰模式越明确。相反没有任何变化点也硬套模式代码不仅没变好还会让人看不懂这就是过度设计。5.2 别把模式当目的过犹不及这些年见过太多设计模式中毒的代码某个只有三个方法的业务类硬是套了个抽象工厂加两个接口加三个实现类代码量翻了三倍可读性却不升反降。设计模式的目的是让代码更清晰、更好扩展不是为了显得高级。判断是否该用模式就一条标准如果将来这个点大概率要变化就值得考虑模式顾虑。这个点根本稳定不变那就直接写场景别绕。5.3 一张图记住整个模式体系纯文字版可以把23个模式理解成一个三层协作模型底层是创建型负责生产零件。中层是结构型负责把零件组装成模块。上层是行为型负责模块之间的协作消息。实际项目里一个设计得好的系统通常是这三类模式混用的。我见过一个比较好的库存系统创建商品用工厂商品信息展示用代理控制权限库存扣减的算法用策略支撑库存变化的消息用观察者广播。设计模式之间本来就不是互相排斥的它们描述的是处于不同问题域的解决方案组合使用才是常态。6. 学习路径与工程落地项目里先把这三件事干好6.1 提高效率的学习顺序不推荐从头到尾把23个模式背一遍纯浪费时间。高效的做法是先掌握最高频的7个——单例、工厂、策略、观察者、装饰器、适配器、模板方法。把它们彻底搞清楚并用过一遍就已经覆盖了80%以上日常开发场景。剩下那些模式至少在意识层面知道名字和使用场景等真遇到时再翻开书细看即可。6.2 在项目里引入模式的推荐顺序我的建议是从小处落手逐层扩散分三步先把傻大个if-else抽成策略。找一处你最烦的、最想重构的逻辑把它从业务代码里挪出去变成策略类。这里最容易看到重构带来的价值能建立信心。再把创建对象的地方改成工厂。等项目里的对象创建点开始变多时自然会觉得直接在业务里new很别扭——这就是引入工厂的好时机。熟手工后再尝试装饰器和代理。这两个模式比较绕适合在有AOP经验或阅读过成熟框架源码之后再来实践。用于处理日志、缓存、权限校验这类横切逻辑效果立竿见影。观察者模式建议配合事件机制一起用先画出事件源——订阅方的关系图再落地。模板方法则要克制只对真正流程固定的逻辑才使用。6.3 我在实际项目里的体会最后分享三点经验纯属个人风格仅供参考。第一设计模式是用来处理变的不是用来炫技术的。看到需求第一反应应该是哪里会变而不是我用什么模式。变了才需要有扩展点不变就简单直接。这个顺序一旦颠倒几乎必然产生烂代码。第二写代码先想到读代码的人。模式虽然好但也不能让队友看不懂你的代码。如果团队里大多数人都不熟设计模式引入时最好配上注释或简短说明文档讲清楚这里为什么要这样做。技术选型要让团队整体受益而不是只有自己一个人爽。第三重构时一次只做一件事。改结构就只改结构不要顺手改功能逻辑。测试能帮你守住这一条底线——先写测试再重构重构完跑测试确认行为没变。这个过程虽然慢一点但积少成多后整个代码库的健康度会有质的提升。模式不用全背理解了它们解决什么问题什么时候用、什么时候不用手上自然就有谱。设计模式说到底不是知识是手艺只能靠动手练出来。