ARTICLE DETAIL

资讯详情

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

设计模式零基础入门:23种模式分类、代码实现与面试实战

设计模式零基础入门:23种模式分类、代码实现与面试实战 设计模式这四个字劝退过不少人。我刚接触那会儿翻开书看到23个名字第一反应是这玩意儿是给人背的吗后来踩过几次坑、啃了几轮源码、面试被问过几轮才慢慢摸到门道——设计模式不是靠背的是靠“场景”驱动的。它解决的根本问题就一个当需求发生变化时你的代码改起来是像换灯泡一样轻松还是像拆承重墙一样痛苦。这篇内容是我自己从零基础到能实际用起来的完整思路。不讲玄学不堆概念用大白话把23种设计模式的分类、记忆口诀、核心代码、UML类图、面试题思路全部串起来。不管你是要应付期末考试、软考速记还是写设计模式大作业、准备Java面试这篇文章都够用。收藏好别吃灰。1. 设计模式到底在解决什么问题1.1 从一段让人头大的代码说起先看一段很多新手都写过的代码。假设你做一个商城支付功能第一种写法是这样的public void pay(String type, double amount) { if (wechat.equals(type)) { System.out.println(使用微信支付 amount); } else if (alipay.equals(type)) { System.out.println(使用支付宝支付 amount); } else if (card.equals(type)) { System.out.println(使用银行卡支付 amount); } }这段代码在功能上没问题但问题在“需求变更”的时候。今天加一个云闪付你要改这个方法明天加一个余额支付你还要改这个方法。改来改去要么出现一堆else if要么一不留神把原有逻辑改坏了。这就是典型的违背开闭原则对扩展不开放对修改不关闭。设计模式解决的就是这类问题。它不负责提升代码的性能不负责减少代码的行数它负责的是应对变化——让代码在需求一波波涌来的时候仍然好读、好改、好维护。你想想为什么有人写三年代码还是天天在改bug很多时候不是逻辑难度大而是代码结构烂牵一发而动全身。1.2 面向对象基本功和设计模式的关系如果把写代码比作盖房子那封装、继承、多态就是砖头、水泥和钢筋而设计模式就是施工图纸。只有砖头你只能盖个平房照着图纸施工你才能盖出框架清晰的大楼。当然图纸也不是越多越好一个茅草屋完全没必要上全套摩天大楼图纸。所以零基础学设计模式之前你得先确认自己有这几个基本功知道class和interface的区别知道继承是什么知道什么是向上转型知道多态是怎么实现的。如果不熟建议先花两小时复习一下。不是说设计模式非要很高基础才能学而是“类是模板接口是契约多态是运行时才确定具体干活的人”这几个概念后面所有模式都用得上。1.3 六大设计原则是理解所有模式的总钥匙23种设计模式太多但它们的“指导思想”其实只有六条也就是常说的SOLID原则再加上一个迪米特法则。我把它们翻译成人话单一职责原则SRP一个类只干一件事。类看多了容易糊涂一个类管天管地最后谁都不敢动它。开闭原则OCP对扩展开放对修改关闭。加新功能不靠改旧代码靠加新代码。里氏替换原则LSP子类要能替换父类还不能破坏程序正确性。说白了就是别乱继承。依赖倒置原则DIP面向接口编程依赖抽象不要依赖具体实现。接口隔离原则ISP接口不要太大太全要小而专。胖接口是灾难。迪米特法则LOD不和陌生人说话一个对象应尽量少了解别的对象内部细节。你看拿到任意一个设计模式都可以用这几条原则去解释“为什么要这么设计”。比如策略模式就是把每个算法封装成独立类互相可以替换——这是开闭原则和依赖倒置原则的典型应用观察者模式里被观察者只管通知观察者各自处理自己的逻辑——这是单一职责的体现。原则是“道”模式是“术”。道领会了术就活了。1.4 学设计模式的实际收益别只盯着面试对大部分人来说学设计模式最直接的两个动力是考试和面试。但我自己工作几年后的体会是设计模式更大的价值在于沟通。当你跟队友说“这个通知模块用观察者来做”对方立刻就知道你的意图和代码结构当你在代码里定义了Strategy接口新人进来扫一眼类名大概就知道该往哪里扩展。另一个价值是帮你读懂源码。Spring、MyBatis、Netty这些框架内部大量使用设计模式。如果你不懂模板方法模式看JdbcTemplate的源码会一头雾水不懂责任链模式看Netty的 Pipeline 会直接劝退。学设计模式不是终点它是你打开源码世界的一把钥匙。2. 23种设计模式怎么分类、怎么记2.1 三种分类创建型、结构型、行为型23种设计模式最早出自GoF四人组的《设计模式可复用面向对象软件的基础》它们分成三大类创建型5种解决的是“怎么把对象创建出来更合理”的问题把创建过程从业务代码中解耦出来。结构型7种解决的是“类和对象怎么组合成更大的结构”的问题让组合方式更灵活。行为型11种解决的是“对象之间怎么协作、职责怎么分配”的问题。打个比方创建型关心“零件怎么造”结构型关心“零件怎么拼”行为型关心“拼好的机器怎么协同工作”。2.2 一张表记住全部23种模式下面把23种模式全部列出来每种给了极简一句话定位。类别模式名一句话定位创建型单例模式全局只允许一个实例创建型工厂方法模式定义一个创建对象的接口由子类决定实例化哪个类创建型抽象工厂模式创建一组相关或相互依赖的对象而不指定具体类创建型建造者模式分步骤构建复杂对象构建过程与表示分离创建型原型模式通过复制现有对象来创建新对象结构型适配器模式让接口不兼容的类能一起工作结构型桥接模式把抽象部分和实现部分分离使它们可以独立变化结构型组合模式把对象组织成树形结构对单个对象和组合对象一致对待结构型装饰器模式动态给对象增加功能替代继承结构型外观模式给复杂子系统提供一个统一的门面接口结构型享元模式共享对象减少大量细粒度对象的创建结构型代理模式为对象提供一个替身控制对它的访问行为型策略模式定义一组算法使其可以互相替换行为型模板方法模式父类定义算法骨架子类实现可变步骤行为型观察者模式对象一对多通知状态变化自动广播行为型中介者模式用一个中介对象封装对象之间的交互行为型责任链模式请求沿着处理链传递直到有人处理行为型解释器模式定义语言的文法并解释句子行为型命令模式把请求封装成对象支持撤销、队列等操作行为型状态模式对象行为随内部状态改变而改变行为型迭代器模式顺序访问集合元素而不暴露内部结构行为型访问者模式在不改变元素类的前提下增加对其的新操作行为型备忘录模式捕获并保存对象状态便于恢复配上记忆口诀方便你快速过一遍创建型五兄弟“工抽单建原”——工厂方法、抽象工厂、单例、建造者、原型。结构型七君子“适桥组装外享代”——适配器、桥接、组合、装饰器、外观、享元、代理。行为型十一罗汉“策模观中责解命状迭访备”——策略、模板方法、观察者、中介者、责任链、解释器、命令、状态、迭代器、访问者、备忘录。口诀读起来有点拗口但你先把它读顺再回头对应表格脑中就有了一张“地图”。考试前默写一遍分类能帮你稳稳保住基础分。2.3 每个模式适合什么场景先建个场景脑图记模式不能只记名字要把“模式名”和“场景”绑在一起。我自己的方法是给每个模式贴上场景标签只要你想让某个类全局只有一个实例想到单例。只要你在创建对象时不想让业务代码直接 new想到工厂系列。只要对象构造参数太多太复杂想到建造者。只要有一组可替换的算法或行为想到策略。只要一个对象变化需要通知一堆依赖者想到观察者。只要一段流程骨架固定、细节多变想到模板方法。只要想给对象动态加功能或想控制访问分别在装饰器和代理里选。只要有层层审批、层层过滤的逻辑想到责任链。只要对象有很多状态、状态切换带来行为变化想到状态模式。这层脑图建立起来后你看任何业务需求都会自然浮现对应的模式。后文第5章的速查表我会再给一份更详细的对照。3. 核心模式逐个拆开揉碎代码原理坑23种模式全部展开写篇幅会非常长也容易让零基础的同学消化不良。我挑七个最高频、面试最爱问、项目里最常用的模式把原理和代码讲透。其余模式理解了场景定位用到时再查书完全来得及。3.1 单例模式面试常客坑也是最多的单例模式保证一个类在JVM里只有一个实例。听起来简单写起来处处是坑。最常见的写法是双重检查锁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关键字。为什么需要它因为new Singleton()不是原子操作它分三步分配内存、初始化对象、把引用指向内存。如果没有volatileCPU和编译器可能重排指令导致线程A先让引用指向了内存但对象还没初始化完线程B此时判断instance ! null拿到的就是一个还没构造好的半成品对象。这不是理论问题是真实会踩的并发坑。除了双重检查锁还有三种常见写法饿汉式类加载时就初始化简单安全但可能造成内存浪费、静态内部类利用类加载机制实现懒加载且线程安全、枚举单例天然防反射、防序列化是《Effective Java》作者强烈推荐的写法。面试时还有两个进阶坑大概率被追问反射能破坏单例吗能。反射可以绕过私有构造器强制调用构造方法解决办法是在构造器里加判断如果实例已存在就抛异常。序列化能破坏单例吗能。序列化后反序列化会生成新对象解决办法是实现readResolve()方法让它直接返回已有实例。单例模式在真实框架里到处都是最典型的就是Spring容器里的Bean。Spring默认Bean就是单例的当然Spring容器自己还会再处理一层但它解决的核心诉求是一致的一个组件全局只保留一份避免频繁创建和状态不一致。3.2 工厂方法 vs 抽象工厂把“创建对象”抽出来工厂系列的核心思想是不直接 new把创建对象的逻辑放到专门的类里。为什么因为直接 new 会把“依赖”焊死在业务代码里一旦替换实现类就得改业务代码。先从最简单的简单工厂说起它其实不在23种模式里但理解它有助入门public class PayFactory { public static Pay create(String type) { if (wechat.equals(type)) { return new WechatPay(); } else if (alipay.equals(type)) { return new AliPay(); } throw new IllegalArgumentException(未知支付方式); } }简单工厂把if-else集中挪到了一个地方比散落在业务代码里强但它违背开闭原则——加一个支付方式还是要改PayFactory的if-else。工厂方法模式进一步解决这个问题。它把工厂抽象成接口每个产品对应一个工厂public interface PayFactory { Pay create(); } public class WechatPayFactory implements PayFactory { Override public Pay create() { return new WechatPay(); } } public class AliPayFactory implements PayFactory { Override public Pay create() { return new AliPay(); } }这样新增支付方式时你只需要新增XxxPay和XxxPayFactory完全不改老代码。这就是开闭原则的落地。抽象工厂模式则更进一步它针对“一族产品”的创建。举个经典例子一家手机组装厂既生产手机又生产充电器、数据线。如果是苹果代工厂生产的是苹果手机、苹果充电器、苹果数据线如果是华为代工厂生产的是华为手机、华为充电器、华为数据线。这种“一整套相关产品”的创建用抽象工厂public interface Factory { Phone createPhone(); Charger createCharger(); } public class AppleFactory implements Factory { Override public Phone createPhone() { return new IPhone(); } Override public Charger createCharger() { return new AppleCharger(); } }一句话区分工厂方法是用一个工厂造一种产品抽象工厂是用一个工厂造一整套产品。日常开发中工厂的变体很多比如Spring的BeanFactory、日志框架的LoggerFactory本质都是在做“不直接new把创建权交给工厂”这件事。3.3 策略模式消灭业务if-else的利器策略模式是我项目里用得最多的模式。还是回到开头的支付场景用策略模式重构public interface PayStrategy { void pay(double amount); } public class WechatPay implements PayStrategy { Override public void pay(double amount) { System.out.println(使用微信支付 amount); } } public class AliPay implements PayStrategy { Override public void pay(double amount) { System.out.println(使用支付宝支付 amount); } } public class PayContext { private PayStrategy strategy; public void setStrategy(PayStrategy strategy) { this.strategy strategy; } public void executePay(double amount) { strategy.pay(amount); } }客户端使用时只需要选择策略、注入上下文、调用方法PayContext context new PayContext(); context.setStrategy(new WechatPay()); context.executePay(99.0);每个支付方式是一个独立策略类新增加一种支付方式就是新增一个类原来所有代码都不用动。这就是“开闭原则”实打实的体现。策略模式的本质是把算法族封装起来让它们可以互相替换。JDK里最典型的例子是Comparator你给Collections.sort传入不同的Comparator实现排序算法骨架不变具体比较策略却可以千变万化。这里容易和工厂模式搞混工厂是决定“创建谁”策略是决定“执行谁”。支付场景里工厂解决“按类型创建哪个支付对象”策略解决“创建出来之后怎么算法化地执行”。两者经常配合使用工厂负责按条件创建策略对象策略对象负责具体的算法执行。面试时能说出这一层配合关系是加分项。3.4 观察者模式一对多通知的原子弹观察者模式解决的是“一对多依赖”问题一个对象状态变了所有依赖它的对象自动收到通知。经典的例子是气象站气象数据一变显示屏、手机App、网站都要同步更新。手写一个最简单版本public interface Observer { void update(float temperature); } public interface Subject { void registerObserver(Observer observer); void removeObserver(Observer observer); void notifyObservers(); } public class WeatherData implements Subject { private ListObserver observers new ArrayList(); private float temperature; Override public void registerObserver(Observer observer) { observers.add(observer); } Override public void removeObserver(Observer observer) { observers.remove(observer); } Override public void notifyObservers() { for (Observer observer : observers) { observer.update(temperature); } } public void setTemperature(float temperature) { this.temperature temperature; notifyObservers(); } }这段代码里WeatherData不关心观察者到底是谁、要做什么它只负责遍历通知。新增一个观察者不需要改WeatherData只要实现Observer接口并注册进来就行。这就是观察者模式的核心价值发布者和订阅者之间解耦。如果你接触过消息队列会发现观察者模式就是发布-订阅模式的同步版。Spring里的ApplicationEventEventListener就是观察者模式的事件机制。你在Service里发布一个订单创建事件邮件服务、短信服务、积分服务各自监听这个事件彼此互不干扰。套用一句话观察者模式关心的是“东西变了怎么让所有相关方都知道”。3.5 模板方法模式固定流程多变细节模板方法模式太好用了而且你其实早就在用它只是没意识到。它的思路是父类定义一个算法的骨架把其中某些步骤延迟到子类实现。子类不能改变算法结构但可以重定义算法里的特定步骤。经典例子是冲泡饮品。不管泡咖啡还是泡茶流程都是固定的烧水、冲泡、加料。其中烧水的逻辑完全一样冲泡和加料则各不相同public abstract class Beverage { public final void prepareRecipe() { boilWater(); brew(); pourInCup(); if (wantCondiment()) { // 钩子方法 addCondiment(); } } protected abstract void brew(); protected abstract void addCondiment(); private void boilWater() { System.out.println(烧水); } private void pourInCup() { System.out.println(倒入杯子); } // 钩子方法子类可覆写决定是否执行某步骤 protected boolean wantCondiment() { return true; } } public class Tea extends Beverage { Override protected void brew() { System.out.println(泡茶叶); } Override protected void addCondiment() { System.out.println(加柠檬); } }注意那个wantCondiment()方法它叫钩子方法。钩子方法的妙处在于父类留了一个“可选项”给子类子类通过覆写钩子方法可以灵活决定算法中的某些步骤要不要执行。比如今天不想加料就返回false。模板方法模式在框架源码里太常见了。Spring的JdbcTemplate把“获取连接、执行SQL、处理结果集、关闭连接”这个固定流程全部封装好只把RowMapper结果集如何映射成对象留给开发者实现。AbstractQueuedSynchronizerAQS也是典型代表同步器的获取锁骨架固定子类只需要实现tryAcquire、tryRelease这些细节。什么时候用模板方法当你发现一段流程反复出现大部分逻辑一样只有中间几步不同的时候。合并重复代码、抽出骨架、让变化的部分开放给子类这就是模板方法的价值。3.6 装饰器模式 vs 代理模式长得像意图差之千里这两个模式非常容易混淆因为它们在代码结构上都是“把一个对象包在另一个对象里面”。但意图完全不同装饰器模式动态地给对象添加职责比如给咖啡加奶、加糖。代理模式控制对对象的访问比如明星的经纪人并不给明星加才艺而是替明星挡事情。看代码就清楚了。装饰器模式的典型写法Java IO流里到处都是BufferedInputStream bis new BufferedInputStream(new FileInputStream(a.txt));BufferedInputStream装饰了FileInputStream给原本的文件流增加了缓冲能力。你还可以再包一层DataInputStream增加读基本数据类型的能力。一层套一层功能叠加这就是装饰器。而代理模式以最简单的静态代理为例public class Proxy implements Subject { private RealSubject realSubject; Override public void request() { System.out.println(访问前做一些控制); realSubject.request(); System.out.println(访问后做一些处理); } }代理强调的是控制访问代理持有一个真实对象在调用真实对象前后可以加权限校验、日志记录、性能统计等等。Spring AOP的底层就是动态代理你声明一个Transactional框架在运行时给你生成一个代理对象在方法调用前后帮你开启和提交事务。面试被问“装饰器和代理有什么区别”我给你一个稳妥的答题结构先说共同点——都是包装模式结构上都是持有另一个对象再说核心区别——装饰器专注于增强功能代理专注于控制访问最后举例IO流是装饰器Spring AOP是代理。这样答既有层次又有落地场景。剩下的模式里建造者模式在对象字段极多时非常好用Lombok 的Builder就是它状态模式适合“对象有多个状态、每个状态行为不同”的场景比如订单状态机责任链模式适合过滤器、审批流MyBatis 的拦截器链、Servlet 的 FilterChain 都是它的实例。有需要时按场景去查即可核心思想你已经有了。4. UML类图怎么读看得懂才能聊得清4.1 类图的六种关系先记住画法网上讨论设计模式时动不动就贴UML类图。很多零基础同学看到一堆箭头直接劝退。其实UML类图就六种关系搞懂画法剩下的全是看图说话。关系箭头表示记忆关键词泛化继承空心三角箭头 实线指向父类“是”一种继承实现空心三角箭头 虚线指向接口“实现”接口关联普通实线箭头知道对方存在长期持有聚合空心菱形 实线箭头整体和个体可分离如学校和老师组合实心菱形 实线箭头整体和部分同生共死如人和心脏依赖虚线箭头临时用到对方如方法参数聚合和组合最让人头大。我的记忆办法是空心菱形是“松耦合的拥有”实心菱形是“强绑定的拥有”。学校倒了老师还是老师这是聚合人没了心脏的意义也就没了这是组合。画法记牢之后看类图就能快速提取信息箭头从哪指向哪谁依赖谁谁继承谁一目了然。4.2 举一个例子观察者模式的类图关系我们不画图用文本把观察者模式的类关系列一遍你看完就能举一反三Subject接口 |-- WeatherData实现 Subject 定义了registerObserver / removeObserver / notifyObservers Observer接口 |-- DisplayA实现 Observer接口 |-- DisplayB实现 Observer接口 |-- DisplayC实现 WeatherData 会持有 ListObserver这是“关联关系” WeatherData 调用 Observer.update()这是“依赖关系”看一眼这样的文本类图你就能明白WeatherData面向Observer接口编程它并不关心到底是DisplayA还是DisplayB这就是依赖倒置原则在类图上的体现。面试时如果让你画观察者模式类图你只要把这四行关系画对思路就是清晰的。4.3 从类图反推设计模式的方法有时候面试官不直接问“什么是策略模式”而是给你一张UML类图让你判断这是什么模式。这时候别慌看几个关键特征有一个接口底下多个实现类上下文持有接口引用并调用它——大概率是策略模式。接口有多个实现类并且接口定义了某个算法的骨架子类只改部分步骤——大概率是模板方法模式。一个类持有大量订阅者接口状态变化时循环调用订阅者的方法——大概率是观察者模式。一个类通过构造器接收另一个类层层包裹每次包一层增加功能——大概率是装饰器模式。一个类和另一个类实现同一个接口并且该类内部持有同接口类型的真实对象——大概率是代理模式。这个反推能力特别重要软考和期末考喜欢出这种题。练法也很简单把23种模式的UML图全部过一遍不看图例只看类之间的关系自己说一遍结构特征。说不上来的回头再查两三轮下来你看到任何类图都能秒识别。5. 应用场景与面试题实战5.1 模式-场景速查表工作中直接翻很多同学学完设计模式最大的困惑是“学了不知道往哪用”。我整理了一份高频场景速查表你遇到类似需求直接对号入座。需求场景推荐模式理由某个类全局只能有一个实例单例避免重复创建保证状态一致不想在业务代码里直接 new 对象工厂方法/抽象工厂创建过程解耦替换方便创建对象字段太多参数容易搞错建造者链式调用可读性好对象创建成本高很多地方要复用同一批对象享元池化思想复用实例一套算法可以在运行时灵活切换策略算法独立封装互相替换日志输出、缓存操作、权限控制等横切逻辑代理不侵入业务代码统一控制给对象动态增强功能装饰器比继承灵活组合优于继承新旧接口不兼容需要桥接适配器转换接口让两边正常协作复杂子系统只需暴露一个简单入口外观门面封装降低使用成本多个状态对应不同行为状态状态转移清晰消除大量if请求需要多个处理器依次处理责任链解耦发送者和接收者对象状态变化需要通知一堆对象观察者一对多广播发布订阅固定流程部分步骤各异模板方法骨架复用细节交给子类需要记录操作历史并支持撤销备忘录命令保存快照命令封装操作这张表记熟日后面试题里“这个场景用什么模式”基本难不倒你。5.2 高频面试题和答题思路面试题是设计模式学习的重要试金石。我把最高频的几类问题整理一下并给出参考回答框架。问题一手写一个线程安全的单例。这是最基础但挂了无数人的题。建议背熟双重检查锁写法并且主动解释volatile的作用再补一句“更推荐用枚举或静态内部类”。主动说关键点面试官会觉得你理解得深。问题二Spring里用到了哪些设计模式这是一道综合性考题答得好非常出彩。参考回答Bean默认单例是单例模式BeanFactory是工厂模式AOP用动态代理JdbcTemplate用模板方法ApplicationEvent用观察者HandlerInterceptor用责任链MyBatis里SqlSessionFactory是工厂Executor骨架是模板方法等等。你不需要把Spring所有源码都背下来挑三四个你能讲清场景的例子就够了关键是说明“在哪个模块、解决什么问题”。问题三装饰器模式和代理模式的区别。这个上面已经讲透。答题要点都是包装但意图不同装饰器增强功能代理控制访问举例说明。问题四如果订单支付方式特别多你怎么设计这是一个典型的场景设计题零基础也能答。参考思路定义支付策略接口每种支付方式一个实现类用简单工厂或工厂方法根据支付方式类型创建策略来源数据变化时通过观察者通知订单状态的变更如果支付流程有很多步骤验签、调第三方、回调、更新库存用模板方法把骨架固定。能答到这个层次面试官基本认可你具备设计意识。问题五如何消灭项目里满天飞的if-else别一上来就说“全部用设计模式”这是过度设计的味道。建议答案是先按场景分类如果if-else在根据类型创建对象用工厂方法如果if-else在根据类型执行不同算法用策略模式如果if-else在根据状态切换行为用状态模式如果分支后面还会继续加优先考虑模式重构。如果分支是稳定的业务规则硬套模式反而增加复杂度。问题六为什么要面向接口编程这道题考察的是设计原则。参考回答面向接口编程是依赖倒置原则的体现。调用方不依赖具体实现只依赖抽象这样实现类可以随时替换系统扩展性和可测试性都大幅提升。配合策略模式、工厂模式举例说服力更强。5.3 期末、软考、大作业怎么临时抱佛脚也抱出水平如果你是赶期末或软考时间不多我建议按下面的顺序执行第一步先把第2.2节的分类表和口诀背下来做到看到模式名能说出它是创建型、结构型还是行为型这一两天就能做到。软考和期末的选择题、判断题很喜欢考分类。第二步重点画三个类图关系策略模式、观察者模式、模板方法模式。这三个出图题的几率最高。我就见过期末考“画出观察者模式UML类图并简述各角色职责”的题目上面4.2节的文本关系你背下来画图题基本稳了。第三步每个模式准备一个“一句话场景”。比如“某系统需要在运行时切换加密算法用什么模式——策略模式”。软考案例分析特别喜欢这种模式识别题你把我那张速查表过两遍识别题就能应付。第四步如果是大作业选题策略是选一个“天然适合展示多种模式”的系统。比如在线商城支付用策略模式订单创建过程用建造者模式库存扣减通知用观察者模式登录拦截用责任链模式。写代码前先画出类图再把类图对应到每一个模式代码实现起来反而更快。大作业的重点不是代码量是“设计思路清晰、模式应用有理有据”一份类图加一份模式应用说明比硬写几千行代码更得分。6. 常见问题与避坑技巧6.1 误区一背得出23个名字却说不出应用场景这种“纸上谈兵”型学习者特别多。症状是口诀背得滚瓜烂熟面试官问“你项目里哪里用了观察者模式”立刻卡壳。破解方法只有一个强迫自己每个模式都去真实代码里找一个案例。比如单例——你项目里的配置文件读取工具类是不是应该单例策略——你项目里有没有一套算法或规则经常要换观察者——你项目里有没有“某个事件发生后要通知很多模块”的场景找到一个写下来画个图这个模式就长在你脑子里了。6.2 误区二为了用而用过度设计刚学会设计模式的人很容易“手里拿着锤子看什么都像钉子”。一个只有十几个方法的Contoller硬拆成工厂策略观察者结果代码量翻倍可读性暴跌。我的原则是没有变化的需求就不要套模式。设计模式是应对变化的如果某个分支未来三年都不会变老老实实写if-else完全没问题。什么时候重构当你发现加需求越来越累、改代码容易改坏的时候再引入模式。重构的时机比模式本身更重要。6.3 误区三只看不练类图不会画代码写不出设计模式是技能技能就得靠练。我建议你手上的练习项目至少包含这几个模式支付模块用策略工厂日志通知用观察者数据导入流程用模板方法权限拦截用代理或责任链。不用面面俱到但每个模式至少要自己完整写一遍并画出对应的UML类图。写一遍比看十遍都管用。6.4 我在实际项目里是怎么识别重构契机的说点实战经验。我通常会在三种情况下考虑引入设计模式第一种if-else超过三层而且分支还在不断增加。比如支付方式每季度加一种这时候不抽策略模式代码会越来越难读。第二种一个类里职责特别多改动一个点会影响一片。这时候按单一职责拆类搭配观察者或命令模式理清交互。第三种多个类里有重复的流程代码比如每个Service都要做“参数校验-权限校验-业务处理-日志记录”抽个模板方法或责任链省掉大量重复劳动。判断标准始终是“是否降低了修改成本”。如果引入模式后下一次加需求更轻松了那就是对的如果只是把简单问题复杂化赶紧退回去。设计模式没有银弹适合的才是最好的。6.5 面试手写代码时的三个小细节最后聊点面试手撕代码的细节。写单例的时候一定要写volatile哪怕面试官没提示你主动加上并解释原因这题就基本满分了。写策略模式的时候记得展示“策略接口 上下文 客户端”三层结构漏掉上下文策略模式的价值就体现不出来。写观察者模式的时候注意让发布者面向观察者接口编程不要在发布者里 hard code 具体观察者类。这三个细节都是面试官考察你“到底是背模板还是真理解”的分水岭。我自己学设计模式最大的体会是它不是一个需要“精通”的学科而是一套用来解决问题的工具箱。你不需要也没必要把23种模式全部背得滚瓜烂熟你只需要知道每个箱子里装的是什么遇到问题知道该去开哪个抽屉就够了。用得多了你会慢慢发现很多模式不是设计出来的而是重构出来的——一开始代码都很朴素随着需求不断变化那些优秀的设计结构会自然浮现。如果你也有学了不用、看了就忘的经历别焦虑先从手边最简单的一个if-else开始试着把它换成一个策略模式你会感受到那个“从此不再改旧代码”的爽快感。
返回列表