☰
设计模式复习笔记:从可维护性到OOP原则与场景应用
2026/10/14 3:17:28 网站建设 项目流程

复习到“面向可维护性的设计模式”这一块,很多同学的第一反应是把二十多个模式的意图、结构图、优缺点全背下来。我当年也这么干过,结果一到场景题,题目换个说法就懵了,不知道到底该用哪个模式。后来我把思路彻底换了一遍——先搞清楚“可维护性”这三个字到底在说什么,再回头看这些模式,一切就都顺了。

这篇复习笔记就是按这个思路整理的。它不是什么官方教材的浓缩版,而是我在复习和实际写代码过程中总结出来的理解方式:每个模式解决什么问题、为什么它能提升可维护性、考试和面试里怎么识别场景。适合正在复习软件构造、准备期末考或者面试前突击设计模式的同学,也适合写了两三年业务代码但总觉得“一改需求就牵一发动全身”的朋友。

1. 先搞明白:可维护性到底在维护什么

1.1 三个字背后的真实成本

可维护性(Maintainability)不是“代码能跑就行”的反义词,它说的是:当需求发生变化时,你需要改动多少代码、改动会不会引入新bug、新同学接手时需要多久才能看懂。

我见过太多项目,功能一切正常,但每次加需求就跟拆炸弹一样。改一个支付逻辑,牵扯到订单状态、库存、通知、对账,改完还得回归测试半天。这种系统的“可维护性”几乎为零。反过来说,一个可维护性好的系统,新需求来的时候,你通常只需要在一个地方做修改,或者新增一个类、一个模块就搞定了,其他地方完全不用动。

设计模式之所以重要,就是因为它给了我们一套经过验证的“改动隔离”方案。每个模式都对应着一种“变化的轴”——有的是对象创建方式在变,有的是算法在变,有的是对象间的交互在变。模式把这些变化点封装起来,让变化不扩散到整个系统。

1.2 六大原则:模式的灵魂

模式是招式,原则是内功。考试里经常让你“指出某模式体现了哪些原则”,所以这六个原则一定得背熟,而且要能说出“为什么”。

  • SRP 单一职责原则:一个类只因为一个原因而变化。这是最基础的一条,也是被违反得最多的一条。
  • OCP 开闭原则:对扩展开放,对修改关闭。可维护性的核心目标,大多数模式都在为它服务。
  • LSP 里氏替换原则:子类必须能替换父类且行为不破坏。继承用得不对,维护就是噩梦。
  • ISP 接口隔离原则:客户端不该依赖它不需要的接口。胖接口是耦合的温床。
  • DIP 依赖倒置原则:依赖抽象,不依赖具体实现。这是解耦的钥匙。
  • LOD 最少知识原则(迪米特法则):一个对象应该尽量少地知道其他对象的内部细节。也叫“不和陌生人说话”。

把这六条理解了,你会发现设计模式本质上就是这些原则在不同场景下的具体落地。比如策略模式落地了OCP和DIP,观察者模式落地了OCP和最少知识原则,工厂模式落地了DIP和SRP。

提示:考试答题时,问到“为什么这个模式能提升可维护性”,别只背模式的官方定义,往原则上靠。能说出“通过引入抽象层隔离变化,满足开闭原则”这种话,得分明显不一样。

2. 创建型模式:对象从哪来,决定了后面好不好改

2.1 工厂方法 vs 抽象工厂:别搞混,场景完全不同

工厂方法(Factory Method)的核心是:定义一个创建对象的接口,让子类决定实例化哪个类。它把一个类“要用什么具体产品”的决定权,从调用方转移到了子类里。

public interface Parser { Document parse(String content); } public abstract class DocumentParser { public Document parseFile(String path) { String content = readFile(path); Parser parser = createParser(); return parser.parse(content); } // 子类决定用哪个Parser protected abstract Parser createParser(); }

这段代码里,parseFile的流程是固定的,但createParser是个“钩子”,子类可以返回不同的解析器。以后要支持新格式,新增一个子类就行,不用动公共流程。

抽象工厂(Abstract Factory)则是“生产一族相关产品”的工厂。比如一个UI框架要支持深色主题和浅色主题,每套主题都有按钮、输入框、弹窗,这时候用一个ThemeFactory,让DarkThemeFactory和LightThemeFactory分别生产配套的组件。

区别很简单:工厂方法针对“一个产品等级结构”,抽象工厂针对“多个产品等级结构的组合”。考试常考的是给你一个场景,让你判断该用哪个——看到“多个产品族”就是抽象工厂,看到“一个产品有多种实现且创建流程固定”就是工厂方法。

2.2 单例模式:维护性杀手,用之前想清楚

单例(Singleton)可能是被滥用最严重的模式。它的本意是保证一个类只有一个实例,比如全局配置、连接池。但全局状态本身就是可维护性的敌人——你永远不知道谁改了它,测试也没法隔离。

我的建议是:复习时掌握它的写法(懒汉、饿汉、双重检查锁、静态内部类),但实际项目里优先用依赖注入的方式管理单例生命周期,而不是自己写getInstance()。如果用,至少保证它是无状态的,或者状态是只读的。

考试里还常考线程安全问题。双重检查锁定(Double-Checked Locking)用volatile修饰实例变量,这个细节一定要记住,很多同学丢分就丢在没写volatile上。

public class ConfigManager { 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; } }

2.3 建造者模式:参数多到爆时的解药

当一个对象有七八个参数,其中一半还是可选的时候,构造函数重载会写到怀疑人生。建造者(Builder)模式把对象的构造过程拆成一步步的方法调用,既清晰又能约束必填项。

Pizza pizza = new Pizza.Builder() .size(12) .cheese(true) .pepperoni(true) .bacon(false) .build();

这个模式在复习时要注意和工厂模式区分:工厂模式是“一步到位给你一个完整对象”,建造者模式是“分步定制,最后组装”。可维护性角度来说,建造者模式最大的好处是新增可选属性时不用改构造函数,只加一个方法,调用方代码几乎不受影响。

3. 结构型模式:让类的形状可以“拼装”

3.1 适配器:新旧代码之间的翻译官

适配器(Adapter)解决的是“接口不兼容”的问题。你有一个老系统的接口,新代码期望另一种接口,又不能大动干戈改老代码,那就写一个适配器做转换。

public class LegacyOrderService { public String queryOrder(String orderId) { ... } } public interface OrderQuery { OrderVO query(String id); } public class OrderQueryAdapter implements OrderQuery { private LegacyOrderService legacyService; public OrderVO query(String id) { String result = legacyService.queryOrder(id); // 转换成OrderVO return convert(result); } }

这个模式在“软件构造”课里是重点,因为课程强调“增量式开发”——在不修改已有代码的前提下扩展功能。适配器完美体现了OCP:老代码不动,新代码面向新接口,适配器在中间做桥接。

3.2 装饰器:不想动原类又想加功能

装饰器(Decorator)和继承是两种“加功能”的思路。继承是静态的、编译期确定的,而且一多层就产生类爆炸。装饰器是动态组合,运行时一层层包裹,每个装饰器只负责一项横切关注点。

典型的例子就是Java IO流:new BufferedInputStream(new FileInputStream(path))。BufferedInputStream就是一个装饰器,它不改变文件读取的核心逻辑,只加缓冲功能。

public interface Notifier { void send(String message); } public class EmailNotifier implements Notifier { public void send(String message) { ... } } public class SmsNotifierDecorator implements Notifier { private Notifier wrapped; public void send(String message) { wrapped.send(message); sendSms(message); } }

要加分可以再加一层,比如WechatNotifierDecorator,完全不用改EmailNotifier。这种“组合优于继承”的思想,就是装饰器对可维护性的最大贡献。

3.3 组合模式:树形结构的统一操作

组合(Composite)模式让“单个对象”和“组合对象”拥有一致的操作接口。典型场景是文件系统、组织架构、菜单树。

public interface Component { void show(); } public class Leaf implements Component { ... } public class Composite implements Component { private List<Component> children = new ArrayList<>(); public void add(Component c) { children.add(c); } public void show() { // 处理自身 for (Component c : children) c.show(); } }

调用方根本不用关心自己处理的是叶子还是树节点,统一调用show()就行。新增节点类型时,只要实现Component接口就行,不用改调用逻辑。这就是“部分-整体”结构的统一性带来的维护价值。

3.4 外观模式:给复杂子系统开一扇门

外观(Facade)模式特别简单,但特别实用。它给一个复杂的子系统提供一个统一的、简化的接口。调用方不需要知道子系统内部有几个类、互相怎么协作,只知道一个Facade.placeOrder()就够了。

维护性的提升在于:子系统内部再怎么重构,只要Facade的接口不变,调用方就不受影响。这相当于给“变化”和“依赖”之间砌了一堵墙。

考试里容易出现一个判断题:外观模式是不是违反了最少知识原则?恰恰相反,外观模式正是最少知识原则的体现——调用方不需要和子系统里的所有类打交道,只和一个外观类说话就可以了。

4. 行为型模式:把“变化的行为”变成“稳定的结构”

4.1 策略模式:算法的替换开关

策略(Strategy)模式解决的是“算法族”的替换问题。不用策略模式的时候,我们习惯用if-else或switch去选择算法,每次加新算法都要改那个分支结构。用策略模式之后,算法被封装成独立的类,通过一个公共接口调用。

public interface DiscountStrategy { double calculate(double amount); } public class VipDiscount implements DiscountStrategy { public double calculate(double amount) { return amount * 0.8; } } public class NormalDiscount implements DiscountStrategy { public double calculate(double amount) { return amount; } } public class OrderService { private DiscountStrategy strategy; public OrderService(DiscountStrategy strategy) { this.strategy = strategy; } public double finalAmount(double amount) { return strategy.calculate(amount); } }

以后加一个“节日促销折扣”,新增一个类就完事,OrderService一行都不用改。这是OCP最典型的落地。复习时要能把策略模式和状态模式区分开——策略模式是算法可以替换,状态模式是行为随状态变化。

4.2 观察者模式:发布-订阅的解耦

观察者(Observer)模式让“状态变化”的通知逻辑不再耦合在具体对象里。发布者只管通知,不关心谁在听;订阅者只管响应,不关心通知从哪来。

考试里常考Java的Observer/Observable接口,还有GUI事件监听模型也是观察者模式的实例。可维护性的核心价值在于:新增一种订阅者,不需要修改发布者代码,两边通过接口解耦。

public interface OrderListener { void onOrderCreated(Order order); } public class OrderService { private List<OrderListener> listeners = new ArrayList<>(); public void addListener(OrderListener l) { listeners.add(l); } public void createOrder(Order order) { // 创建订单逻辑 for (OrderListener l : listeners) l.onOrderCreated(order); } }

这里要注意一个细节:观察者模式虽然解耦了发布者和订阅者,但通知顺序、异常处理、线程模型都可能导致新问题。实际项目中,这些监听器往往通过配置注册,而不是在业务代码里硬编码addListener,否则又变成了一种隐式耦合。

4.3 模板方法:骨架稳定,细节可变

模板方法(Template Method)定义了一个算法的骨架,把一些步骤延迟到子类实现。它和策略模式的区别是:策略模式是“整个算法替换”,模板方法是“算法骨架固定,部分步骤可变”。

public abstract class DataImporter { public final void importData(String file) { validate(file); parse(file); save(); } protected abstract void parse(String file); protected void validate(String file) { // 默认实现 } protected abstract void save(); }

这种模式能提升可维护性的关键在于“控制反转”——父类决定流程,子类只提供细节。流程变了改父类,细节变了改子类,互不干扰。复习时要注意final关键字和protected访问级别的组合使用,这是模板方法实现封装性的关键。

4.4 状态模式:状态机改起来不再头疼

状态(State)模式把“对象在不同状态下有不同行为”这个逻辑,从一堆if-else里解放出来。每个状态一个类,状态之间的转移由状态类自己决定。

这个模式对可维护性的提升非常直观:没有状态模式时,每加一个状态,你要回到那个巨大的条件分支里翻找,非常容易漏改。有状态模式后,加一个状态就是加一个类,改状态转移就改对应状态类的代码,隔离性很好。

考试常出的考点是:状态模式 vs 策略模式。记一个核心区别:策略模式的客户端自己决定用哪个策略;状态模式中,状态迁移是状态对象内部触发的,客户端感知不到状态的变化。

4.5 访问者模式:双刃剑,慎用

访问者(Visitor)模式可以在不修改已有类的前提下,为一系列类增加新的操作。听起来很美,但它有个巨大的代价:新增一个“被访问的类”时,所有访问者都要改,违反了OCP。

它的适用场景很窄——对象结构稳定(类很少新增),但操作经常增加。比如编译器里对语法树的各种操作:类型检查、代码生成、优化,语法树的节点类型基本固定,但操作不断增多,这时访问者模式就很合适。

复习到访问者模式时,我的建议是:理解意图和结构,但答题时遇到不确定的场景,别硬套。它是我见过实际项目里被用得最少、也最容易用错的模式。

5. 复习时的常见误区与应试要点

5.1 误区一:看到需求变化就往模式上套

这是所有新手都会犯的错。模式不是装饰品,每个模式都有代价——类的数量变多、结构变复杂、阅读成本变高。一个只有两处变化的小场景,用策略模式反而把简单问题复杂化了。

我复习时给自己定了一个判断标准:只有当变化是“频繁的、多维度的、且代码已经因为变化而变得难以维护”时,才值得引入模式。如果在纠结要不要用,那就先用最简单的方式写,等第二次发生同样的变化时再重构。

5.2 误区二:只背意图,不理解结构

考试里常考“画出某模式的结构图”或者“根据结构图判断是哪种模式”。只背意图不记结构,看到UML图就傻眼。建议至少把这些模式的参与者(抽象类、具体类、客户端)和它们之间的关系画上两三遍:

模式关键参与者核心关系
策略Context, Strategy, ConcreteStrategyContext持有Strategy接口引用
观察者Subject, Observer, ConcreteObserverSubject维护Observer列表
装饰器Component, Decorator, ConcreteDecoratorDecorator包装Component
工厂方法Creator, Product, ConcreteCreator子类决定创建哪个Product
适配器Target, Adapter, AdapteeAdapter包装Adaptee实现Target
状态Context, State, ConcreteStateContext持有State引用,状态可切换

5.3 答题的万能分析框架

碰到“请为以下场景选择合适的设计模式”这类题,我习惯按四步走:

  1. 找变化点:题目里哪个因素是“可能变化的”?业务规则、算法、对象创建方式、还是对象间交互方式?
  2. 找原则冲突:现有设计哪里违反了OCP或者DIP?哪个if-else分支将来会频繁改?
  3. 匹配模式:变化点是“算法的选择”→策略;变化点是“创建哪种具体类”→工厂;变化点是“状态相关行为”→状态模式;变化点是“通知依赖关系”→观察者。
  4. 说清代价:别只说优点,顺带提一句“引入这个模式后,类的数量增加,但换取的是OCP的满足”,分数会更高。

注意:答题时别把模式名写错或者混写。比如“模板方法”不是“模板模式”,“外观”不是“门面模式”。这些术语在阅卷时都是采分点,写错了很吃亏。

6. 实操中踩过的坑与最后几点体会

6.1 一个真实的重构例子

去年我在一个模拟项目里接手过一段订单处理代码,里面有一个200行的processOrder方法,里面嵌套了四层if-else:判断用户类型、判断商品类型、判断优惠类型、判断支付方式。每次加一种优惠,那个方法就要再深一层,测试同学已经放弃了写这个方法的用例。

我做的第一件事不是套模式,而是先提取变化点。把“优惠计算”单独拎出来,定义成DiscountStrategy接口,每种优惠一个实现类。然后是“通知逻辑”,引入观察者,订单创建后自动通知库存、财务、客服。最后是“支付方式”,用工厂方法封装。

改完之后,processOrder瘦身到三十行,新增优惠不再动主流程,新增通知方也不用碰订单类。这个例子让我真正理解了——模式不是目标,隔离变化才是目标。

6.2 复习顺序建议

如果你时间有限,我建议按这个优先级复习:

  • 第一梯队(必考且高频):策略、观察者、装饰器、工厂方法、模板方法
  • 第二梯队(经常考):适配器、组合、状态、单例、建造者
  • 第三梯队(低频但会出选择题):抽象工厂、外观、代理、访问者、迭代器、中介者

6.3 最后一个小技巧

复习模式的时候,别只对着UML图看。试着问自己一个问题:“如果我现在要给这个系统加一个新功能,哪种模式能让我只新增一个类就完成?”能在脑子里用这个标准过一遍,你就不是在背模式,而是在真正理解可维护性设计了。

我当年就是把二十多个模式挨个用这个“新增功能”的思路过了一遍,配合画结构图、默写关键代码,考试时看到场景题基本不用思考太久,直接就能锁定两三个候选模式。希望这份复习笔记也能帮你把“面向可维护性的设计模式”从背诵题变成真正的理解题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询