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

设计模式实战指南:三大类型与高频模式详解及代码重构

设计模式实战指南:三大类型与高频模式详解及代码重构 ★ FEATURED ARTICLE
2. 按类别理解创建型、结构型、行为型的底层逻辑设计模式分三大类这不是随便分的背后其实是软件设计的三个核心问题对象怎么创建、对象怎么组合、对象怎么协作。我当年学的时候觉得这个分类只是目录后来才发现它是理解模式的钥匙。2.1 创建型模式解决对象“怎么来”的问题创建型模式的核心思想是把对象的创建过程从使用代码中独立出来。为什么要独立举个例子你写了一个工具类里面直接new了某个对象。结果这个对象的构造函数要加参数了你得改多少处调用创建型模式就是帮你把“怎么创建”封装起来让调用方只关心“我要什么”不关心“怎么来的”。包括 5 个模式工厂方法、抽象工厂、单例、建造者、原型。它们各自的侧重点不一样工厂系解决“创建逻辑复杂”的问题单例解决“全局只允许一个实例”的问题建造者解决“对象构造参数太多”的问题原型解决“创建成本高通过拷贝复制”的问题。2.2 结构型模式解决对象“怎么组合”的问题结构型模式关注的是类和对象的组合方式目标是让组合出来的结构既灵活又高效。编程里最常见的需求就是“给现有对象加功能”你怎么加直接改源码破坏了开闭原则。继承扩展有时候会搞得类爆炸。7 个结构型模式里适配器解决“接口不兼容”的问题装饰器解决“动态追加功能”的问题,代理解决“不想直接访问原对象”的问题外观解决“子系统太复杂提供一个简化门面”的问题。它们的共同逻辑是组合优于继承接口隔离变化。2.3 行为型模式解决对象“怎么协作”的问题行为型模式最容易被忽视但其实它是工作中用得最多的一类核心是对象之间的职责分配和通信方式。很多业务 bug 就出在对象之间的交互逻辑乱成一团——你调我我调你谁改了状态别人都不知道。11 个行为型模式里策略解决“算法族可以互相替换”的问题观察者解决“状态变化要通知多个依赖方”的问题模板方法解决“骨架固定、步骤可定制”的问题责任链解决“请求需要多个处理器依次处理”的问题。行为型模式关注的是运行时的行为编排所以它的抽象程度更高理解起来也更烧脑。3. 高频模式逐个拆解从代码到场景的实战对照GoF 的 23 个模式是基础但日常工作真正常用的其实就那七八个。我建议你把高频模式吃透剩下的混个脸熟、知道什么时候去查就够了。下面挑四个代表性最强的模式展开讲。3.1 单例模式看似简单坑最多单例的核心就一句话保证一个类只有一个实例并提供一个全局访问点。典型的应用场景有配置管理器、线程池、数据库连接池、日志对象。基于常见实践的补充很多人以为单例就是“一个静态变量 一个私有构造函数”但实际工程里单例分两种写法——饿汉式和懒汉式多线程环境下的线程安全问题才是真正的考点。饿汉式在类加载时就初始化无并发问题但可能造成资源浪费懒汉式在首次调用时创建需要加锁处理。// 双重检查锁DCL方式兼顾性能与线程安全 public class ConfigManager { // volatile 防止指令重排导致拿到未初始化完成的对象 private static volatile ConfigManager instance; private ConfigManager() { // 加载配置文件 } public static ConfigManager getInstance() { if (instance null) { synchronized (ConfigManager.class) { if (instance null) { instance new ConfigManager(); } } } return instance; } }注意反射可以通过setAccessible(true)强行调用私有构造函数序列化也可能破坏单例。如果不考虑这些极端情况面试时可以提一下但实际业务里用枚举实现单例是更稳妥的方案。这是我在实际项目里踩过的一个坑某次把单例里的成员变量当成了“可在任意地方修改的全局变量”结果多个线程同时写数据被串成了麻花。单例只保证“一个实例”不保证“线程安全”实例内部的字段该加锁还得加锁该用原子类还得用原子类。3.2 策略模式干掉一坨 if-else 的最优解策略模式是我个人在工作中用得最多的模式之一。它的核心是定义一族算法把它们封装起来并且可以互相替换。经典的场景是支付方式选择、营销活动规则、文件导出格式。我试过用策略模式重构一个支付模块原来 30 多个 if-else 嵌套了 3 层每加一种支付方式就得动主流程改完一个地方崩三处。重构后每种支付方式一个策略类主流程只依赖一个策略接口新接入支付宝类的时候不动任何已有代码。// 策略接口 public interface PayStrategy { void pay(BigDecimal amount); } // 具体策略微信支付 public class WechatPayStrategy implements PayStrategy { Override public void pay(BigDecimal amount) { // 微信支付逻辑 } } // 具体策略支付宝 public class AlipayStrategy implements PayStrategy { Override public void pay(BigDecimal amount) { // 支付宝逻辑 } } // 策略上下文持有策略并委托执行 public class PayContext { private final PayStrategy strategy; public PayContext(PayStrategy strategy) { this.strategy strategy; } public void executePay(BigDecimal amount) { strategy.pay(amount); } }策略模式的精髓在于把变化的部分抽出来用组合的方式代替继承。判断一个地方是否适合用策略模式可以看两个信号一是有没有多个类只改行为不改身份二是有没有大量条件分支按类型或枚举值执行不同逻辑。3.3 观察者模式事件驱动架构的基石观察者模式解决的是一对多的依赖关系当被观察者状态变化时所有依赖它的观察者都能得到通知并自动更新。典型场景有消息推送、事件总线、GUI 按钮点击监听、商品库存变化触发的后续动作。以一个电商下单场景为例用户下单后需要同时做扣库存、发短信、发优惠券、记日志。如果这些逻辑都写在下单方法里那下单方法会越来越肿而且每加一个动作都要改订单核心代码。用观察者模式下单成功后只发布一个“订单创建事件”各个监听器自己去处理各自的逻辑。// 事件发布/订阅的简化实现 public class OrderEventPublisher { private final ListOrderEventListener listeners new ArrayList(); public void subscribe(OrderEventListener listener) { listeners.add(listener); } public void publish(OrderEvent event) { for (OrderEventListener listener : listeners) { listener.onOrderEvent(event); } } }实操心得观察者模式有个隐患是观察者执行顺序不确定如果两个监听器之间有依赖关系就容易出诡异 bug。我在项目里处理这个问题时给监听器加了Order(n)注解按顺序执行同时强调监听器之间尽量解耦不依赖彼此的执行结果。3.4 工厂模式从简单工厂到抽象工厂的递进工厂模式的核心是用工厂方法代替直接 new。但很多人分不清简单工厂、工厂方法、抽象工厂三者的区别我把它们的区别整理成了一张对照表。模式核心思想扩展方式适用场景简单工厂一个工厂类根据参数返回不同产品加产品要改工厂类产品种类少、变化少工厂方法每个产品对应一个工厂子类加产品加一个工厂类产品种类多、扩展频繁抽象工厂一个工厂生产一族相关产品加一族产品加一个工厂类系统需要多套产品族用一句话总结简单工厂是“一次判断”工厂方法是“一个产品一个工厂”抽象工厂是“一个工厂管一窝产品”。工作中大部分场景用简单工厂就够了直接套抽象工厂反而显得过度设计。4. 从需求到模式怎么判断“这里该用哪个”很多初学者最大的困惑是模式都背下来了拿到一个需求还是不知道怎么套。我的经验是不要从模式出发找需求要从需求出发找模式。下面用一个真实需求来分析推导过程。4.1 需求信号识别什么时候该动用设计模式我总结了几种常见的“代码坏味道”每种对应到的候选模式构造函数参数越写越多调用方分不清顺序 → 建造者模式一大串 if-else / switch 分支每种分支做不同的事 → 策略模式或工厂模式一个类同时负责多个职责比如既做数据校验又做日志又做展示 → 责任链模式或者门面模式对象创建过程复杂依赖配置、依赖环境 → 工厂模式要给老类加新功能但不想改动已有代码 → 装饰器模式多个类结构相似细节不同→ 模板方法模式这套信号不是一次就能形成的建议每写一次代码都回头看看是否有可以替换成模式的“坏味道”。4.2 一个完整推导实例从 if-else 到策略模式的演进假设现在要做一个消息推送系统支持短信、邮件、站内信三种推送方式。第一版代码很自然地就写成了这样public void push(String type, String content, String target) { if (sms.equals(type)) { // 发短信逻辑 } else if (email.equals(type)) { // 发邮件逻辑 } else if (inbox.equals(type)) { // 发站内信逻辑 } }上面的代码初看没什么问题但业务一定会演化新增微信推送、新增 App 推送、不同渠道的推送还要做不同的日志记录和重试策略。每加一个渠道就要动这个方法而且所有人都要去读这个越来越长的方法。这时候就该引出策略模式。我们把“推送”这个行为抽象成接口把每种渠道封装成独立策略再用一个路由类根据 type 选出对应的策略public interface PushStrategy { void push(String content, String target); } public class SmsPushStrategy implements PushStrategy { } public class EmailPushStrategy implements PushStrategy { } public class InboxPushStrategy implements PushStrategy { } // 策略路由通过 Map 维护类型与策略的映射 public class PushStrategyRouter { private final MapString, PushStrategy strategyMap new ConcurrentHashMap(); public PushStrategyRouter() { strategyMap.put(sms, new SmsPushStrategy()); strategyMap.put(email, new EmailPushStrategy()); strategyMap.put(inbox, new InboxPushStrategy()); } public void route(String type, String content, String target) { PushStrategy strategy strategyMap.get(type); if (strategy null) { throw new UnsupportedOperationException(unsupported push type: type); } strategy.push(content, target); } }重构完后主流程代码量减少了一大半新增微信推送只需要加一个实现类再注册到 Map 里完全不需要改动 router 之外的既有代码。这是策略模式实战价值最直观的体现。补充你可能会问工厂模式和策略模式在这个例子里看起来很像区别在哪我的理解是工厂模式关心“对象怎么创建”策略模式关心“对象创建完之后怎么用它”。实际组合使用也很常见先用工厂创建策略再用策略执行逻辑。5. 常见误区与排查技巧为什么你学了设计模式还是不会用设计模式的学习曲线比较陡大部分人都经历过“背了忘、忘了背”的循环。下面这些坑是我自己和身边同事都踩过的整理出来供你对照自查。5.1 误区一为了用模式而用模式过度设计我见过最夸张的例子一个只有两张表的小项目愣是用了抽象工厂加建造者加门面。代码是“很设计”但维护的人想骂人。设计模式是工具不是装饰品是代码出现“坏味道”时才拿出来用的不是写代码之前就先预定的“秀场”。判断标准很简单引入模式后代码是变得更简洁了还是变得更绕了如果增加两层抽象只是让代码“看起来高级”那就不值得。5.2 误区二只看类图不写代码很多人学设计模式就是把 UML 类图背下来画得清清楚楚但一写代码就卡住。我强烈建议每学一个模式就用 Java 手写一个最小可运行的例子不要抄书上的就自己从零写写不出来再去看示例。写一遍比看十遍管用。5.3 误区三忽略了模式的适用条件每个设计模式都有它适用的“温度范围”。比如模板方法模式适合方法内部骨架固定、步骤可变的情况但如果你发现子类需要覆写一半以上的方法说明这个模板抽象得不对这个模式就不该硬用。观察者模式适合一对多通知但如果只有一个观察者直接依赖就好了何必绕一圈。我个人的排查步骤是先描述现状当前的代码坏味道是什么具体是哪一种再考虑模式哪个模式能消除这个坏味道它引入了哪些新结构权衡取舍引入这个模式带来的复杂度和它解决的问题相比值不值6. 学习路径建议用“三个循环”真正吃透设计模式6.1 第一个循环对照生活类比建立直觉设计模式其实很多都能用生活场景类比。策略模式就像你出门选交通工具地铁、公交、打车都能到具体选哪个运行时定观察者模式就像你关注的主播开播了所有粉丝同时收到通知门面模式就像酒店前台你不需要知道背后有多少部门找前台一个口就行。先建立直觉再看代码模型会顺畅得多。6.2 第二个循环在开源代码里找“实物”学完一个模式后去 Spring、MyBatis、JDK 源码里找它的身影。比如 JDK 里的Runtime类就是单例java.util.Collections#synchronizedList就是静态代理java.util.List的Iterator就是迭代器模式Spring 的BeanFactory就是抽象工厂Spring 的ApplicationListener就是观察者模式。看源码里怎么用比看任何教程都有说服力。6.3 第三个循环重构自己的老代码打开你三个月前写的一个业务模块看看里面的 if-else 能不能用策略模式重构成路由能不能用模板方法把重复的流程抽出来能不能用观察者把耦合在一起的通知逻辑拆开我个人在实际操作中的体会是设计模式不是做项目时必须交的作业而是代码变复杂后的逃生通道。我会在自己的代码里刻意埋一些“重构种子”——比如先写一版简单的实现功能跑通后再看看哪些地方有坏味道然后动手用模式优化。这样练几次模式的适用场景就长在脑子里了遇到需求时自己会冒出来。最后再分享一个小技巧不要强迫自己一次把 23 个模式全背完第一个月熟练掌握单例、工厂、策略、观察者、模板方法这五个就足够了剩下的是遇到问题再去查、去用。真正搞懂设计模式的标志不是“我能画出所有类图”而是“看到一段烂代码我能顺手把它修好”。这个目标靠背是达不到的靠练才行。
阅读完成 · 觉得有帮助?
咨询建站