简介:这份《大话设计模式复习》文档面向具备一定面向对象编程基础、工作1至3年的研发人员,系统梳理设计模式的核心知识体系。内容从设计模式的定义、分类、特点与应用场景切入,详细讲解单一职责、开放-封闭、依赖倒转、里氏代换、迪米特法则、合成/聚合及接口隔离七大设计原则,并深入剖析抽象工厂、策略、外观、适配器、组合、单例、中介者七种常用模式,以及其余十五种模式,每种均配以定义、UML类图、基础代码与实例。文档还横向对比不同模式的差异,总结模式选择的关键维度,并附练习题与模拟试卷辅助巩固。资源为单个docx文件,压缩包约3.66MB,已有191人学习。读者可借此理解模式适用场景,提升代码可维护性与扩展性,并在实际项目中做出更合理的设计选择。
1. 设计模式到底在讲什么:从“每次改需求都像拆炸弹”说起
你有没有过这种经历:接手一个项目,想加一个小功能,结果发现牵一发动全身,改完这里崩那里,最后不得不把整个模块重写。这种“改不动”的痛苦,根源往往不在代码写得丑,而在于结构没设计好。设计模式就是前人踩了无数坑之后,总结出的一套“结构配方”——它不教你写某一行代码,而是告诉你面对某类反复出现的问题时,类和对象该怎么摆、职责该怎么分。
通俗地说,设计模式就像做菜时的“经典菜谱”:红烧肉有红烧肉的做法,糖醋排骨有糖醋排骨的做法,你不需要每次从零发明,照着菜谱来,味道稳定,别人也看得懂。在软件工程和面向对象编程里,这套“菜谱”被反复验证、分类编目,最终形成了 23 种经典模式,分成创建型、结构型、行为型三大类。搞懂它们,你就能在 Java 这类语言里写出可维护、可扩展、可复用的代码,而不是每次需求一变就翻车。
2. 为什么需要设计模式:七个原则才是真正的底层逻辑
2.1 设计原则不是口号,是“避坑指南”
很多人一上来就背 23 种模式,结果用的时候生搬硬套,反而把代码搞复杂了。其实模式是“术”,原则才是“道”。这份资料里重点讲了七大原则,它们才是判断“该不该用模式、用哪个模式”的依据。
单一职责原则(SRP)说的是一个类只干一件事。比如计算器里的加法和减法,各自独立成类,而不是塞进一个“运算类”里。这样改加法不会影响减法,测试也好写。开放-封闭原则(OCP)更关键:软件实体应该对扩展开放、对修改关闭。翻译成大白话就是,加新功能时最好新增代码,而不是去改老代码。策略模式就是典型例子——新增一种折扣算法,只需要写一个新的策略类,原来的策略接口和上下文都不用动。
依赖倒转原则(DIP)要求高层模块和低层模块都依赖抽象,而不是高层依赖低层。比如工厂模式里,客户端只认抽象产品接口,不认具体产品类,这样换一套产品实现,客户端代码一行不用改。里氏代换原则(LSP)说的是子类必须能替换父类,且行为不变。正方形继承矩形在数学上成立,但在代码里如果正方形强制宽高相等,替换矩形时面积计算就可能出问题,这就是违反 LSP 的经典案例。
迪米特法则(LOD)叫“最少知识原则”,一个类对另一个类知道得越少越好。中介者模式就是它的化身:同事类之间不直接调用,全部通过中介者转发,耦合度直线下降。合成/聚合原则(CARP)建议尽量用组合而不是继承来扩展功能,装饰模式就是靠组合装饰器与组件,而不是继承出一堆子类。接口隔离原则(ISP)则要求把大接口拆成小接口,比如“动物”接口拆成“飞行动物”“陆地动物”,避免类被迫实现用不到的方法。
2.2 原则和模式的关系:先有判断,再有选择
这七个原则不是孤立的,它们互相配合。比如你要设计一个可移植的数据访问程序,支持不同数据库,那么依赖倒转原则告诉你依赖抽象工厂接口,合成/聚合原则告诉你用组合把产品对象拼起来,开放-封闭原则提醒你新增产品族时可能会违反它——因为抽象工厂模式增加新产品等级结构需要修改所有具体工厂。你看,原则帮你预判了模式的代价。
所以学设计模式,正确的顺序是先理解这些原则,再去看每个模式是怎么体现或权衡这些原则的。这份资料把七大原则放在模式分析之前,就是这个用意。它让你带着“为什么这样设计”的问题去读后面的模式,而不是死记 UML 类图。
3. 创建型模式:对象怎么“生”出来才不乱
3.1 简单工厂、工厂方法、抽象工厂:三兄弟到底差在哪
创建型模式关注对象的创建方式,最容易被搞混的就是工厂家族。简单工厂模式是一个工厂类根据参数决定创建哪种产品,比如Factory.createProduct("A")。它简单,但新增产品要改工厂类,违反开放-封闭原则。工厂方法模式把创建延迟到子类,每个具体工厂只负责一种产品,新增产品只需新增工厂,符合开放-封闭。抽象工厂模式则是为创建一组相关或依赖的对象族提供接口,比如不同数据库工厂创建 User 和 Department 对象,一个工厂负责一个产品族。
用表格对比更清楚:
| 模式 | 创建对象 | 新增产品 | 新增产品族 | 符合 OCP |
|---|---|---|---|---|
| 简单工厂 | 单一产品 | 改工厂类 | 不适用 | 否 |
| 工厂方法 | 单一产品 | 新增工厂类 | 不适用 | 是 |
| 抽象工厂 | 产品族 | 改抽象工厂和所有具体工厂 | 新增具体工厂 | 部分 |
抽象工厂的典型实例是可移植数据访问程序:IFactory接口声明CreateUser()和CreateDepartment(),SqlServerFactory和AccessFactory分别实现,客户端只依赖IFactory。这样换数据库时,只需换一个工厂实例,业务代码不动。但如果你要新增一种产品(比如新增CreateOrder()),所有具体工厂都得改,这就是它违反开放-封闭原则的地方。
3.2 单例、建造者、原型:创建方式的另类选择
单例模式保证一个类只有一个实例,并提供全局访问点。它适合配置管理、连接池这类场景,但要注意多线程下的线程安全,以及它可能带来的全局状态问题。建造者模式把复杂对象的构建与表示分离,适合参数多、构建步骤固定的对象,比如组装一台电脑,可以分步设置 CPU、内存、硬盘,最后build()。原型模式通过复制现有对象来创建新对象,适合创建成本高、但状态可复制的场景,比如一个初始化很慢的对象,直接克隆比重新 new 快得多。
这三个模式和工厂家族的区别在于:工厂关注“创建什么”,建造者关注“怎么一步步创建”,原型关注“从现有对象复制”。单例则关注“创建几个”。理解这个维度,就不会混用。
4. 结构型与行为型:把类和对象摆对位置
4.1 适配器、装饰器、代理:三个“包装”模式的区别
结构型模式关注类和对象的组合。适配器、装饰器、代理都涉及“包装”一个对象,但目的完全不同。适配器模式是让接口不兼容的类能一起工作,比如一个旧系统的接口和新系统对不上,加个适配器转换一下。装饰器模式是在不改变原类的情况下动态添加功能,比如给一个组件层层包裹装饰器,每层加一点行为。代理模式是控制对对象的访问,比如远程代理、虚拟代理、保护代理,客户端以为在直接调用,其实中间隔了一层。
用一句话区分:适配器是“转换接口”,装饰器是“增强功能”,代理是“控制访问”。组合模式把对象组合成树形结构,表示“部分-整体”的层次,比如文件系统里文件夹和文件可以统一处理。外观模式为子系统提供统一的高层接口,让客户端不用跟一堆类打交道。桥接模式把抽象和实现分离,让它们可以独立变化。享元模式通过共享细粒度对象来节省内存,适合大量相似对象的场景。
4.2 策略、状态、观察者、中介者:行为型模式怎么选
行为型模式关注对象之间的交互和职责分配。策略模式和状态模式结构很像,但意图不同:策略模式是客户端主动选择算法,算法之间互不关心;状态模式是对象内部状态改变时自动切换行为,状态之间可能有转换关系。比如商场收银系统用策略模式,不同折扣策略由客户端选择;而一个订单对象的状态从“待支付”到“已支付”再到“已发货”,用状态模式更自然。
观察者模式和中介者模式也容易混。观察者模式是一对多的依赖关系,当一个对象状态改变,所有依赖它的对象都收到通知,比如事件监听。中介者模式是多对多的交互通过中介者协调,同事类之间不直接通信,比如聊天室里的用户都通过服务器转发消息。责任链模式把请求沿着处理链传递,直到有一个处理者处理它,比如审批流程。模板方法模式在父类定义算法骨架,子类实现具体步骤。命令模式把请求封装成对象,支持撤销、队列等操作。备忘录模式保存对象状态以便恢复,迭代器模式遍历集合,访问者模式在不改变元素类的前提下定义新操作,解释器模式处理文法规则。
5. 常见误区:这些模式你很可能用反了
5.1 误区一:抽象工厂和工厂方法是一回事
易混点:两者都叫“工厂”,都创建对象。为什么混:名字相似,UML 结构也有相似之处。怎么区分:工厂方法只创建一类对象,抽象工厂创建一组相关对象族。抽象工厂的抽象工厂接口里通常有多个创建方法,比如createProductA()和createProductB(),而工厂方法只有一个createProduct()。如果你只需要创建一种产品,用工厂方法;如果需要创建多个相互关联的产品,用抽象工厂。
5.2 误区二:策略模式和状态模式可以互换
易混点:两者都有 Context 和抽象策略/状态接口,具体类实现不同行为。为什么混:结构几乎一样。怎么区分:策略模式的客户端知道所有策略并主动选择,策略之间没有依赖;状态模式的状态转换通常由状态自身或上下文触发,客户端不关心具体状态。策略模式是“选算法”,状态模式是“随状态变行为”。如果你发现代码里有一堆 if-else 判断状态然后执行不同逻辑,那可能是状态模式该上场了。
5.3 误区三:适配器和装饰器都是包装,随便用
易混点:两者都持有被包装对象的引用。为什么混:都叫 Wrapper。怎么区分:适配器改变接口,让不兼容的接口能协作;装饰器保持接口不变,只增强功能。适配器的客户端调用的是适配器的新接口,装饰器的客户端调用的是和原组件一样的接口。如果你需要“转换”,用适配器;如果需要“叠加功能”,用装饰器。
5.4 误区四:单例模式可以随便用
易混点:单例看起来方便,全局访问,还能省资源。为什么混:初学者觉得“只有一个实例”很安全。怎么区分:单例引入全局状态,让单元测试变难,还可能隐藏依赖关系。如果只是想让某个资源只初始化一次,考虑依赖注入而不是单例。单例适合真正需要唯一实例的场景,比如配置管理器,但不要把它当成万能工具箱。
5.5 误区五:设计模式用得越多越好
易混点:学了模式就想到处用,觉得不用就落伍。为什么混:模式被神化了。怎么区分:模式是解决特定问题的工具,不是目标。过度使用模式会让代码更复杂、更难懂。比如一个简单的 if-else 能解决的问题,硬套策略模式反而增加类数量。先判断问题是否反复出现、是否有扩展需求,再决定用不用模式。记住:简单是最终目标,模式只是通往简单的路径之一。
6. 什么时候用、什么时候别用:一个真实场景的取舍
假设你在做一个商场收银系统,需求是支持多种折扣:正常收费、满减、打折。一开始你可能写一个CashSuper类,里面用 if-else 判断折扣类型。这能跑,但每次新增折扣都要改这个类,违反开放-封闭原则。这时候策略模式就合适:定义CashSuper接口,CashNormal、CashReturn、CashRebate分别实现,客户端持有一个策略对象,运行时切换。新增折扣只需新增策略类,不改原有代码。
但如果你只是做一个内部小工具,折扣规则永远只有一种,那用策略模式就是过度设计。直接写死反而更清晰。这就是边界:当变化是常态、扩展是刚需时,用模式;当变化极少、逻辑简单时,别用。
再比如,你要设计一个跨平台的数据访问层,支持多种数据库。抽象工厂模式很合适:IFactory定义CreateUser()和CreateDepartment(),不同数据库工厂实现它。但如果你的系统只连一种数据库,且未来也不太可能换,那抽象工厂就是多余的,直接 new 具体类更省事。
我自己的血泪经验是:曾经在一个小模块里硬套了观察者模式,结果为了通知一个状态变化,绕了三层接口,调试时追调用链追到怀疑人生。后来我把那个模块重写成直接调用,代码量少了一半,可读性反而更好。从那以后我每次想用模式之前,都强制问自己三个问题:这个问题真的会反复出现吗?未来扩展的频率有多高?不用模式的话,最坏情况是什么?如果答案都是“还好”,那我就选择简单方案。
设计模式是工具箱里的扳手,不是每颗螺丝都要用扳手拧。希望这份资料能帮你建立判断力,知道什么时候该拿起哪个工具,什么时候该放下。
本文还有配套的精品资源,点击获取