写这篇博客的起因很简单我背得出单一职责开闭原则依赖倒转这些词但很长一段时间里我看别人写的策略模式、装饰模式、代理模式代码总觉得这些原则像是挂在墙上的标语——知道它存在但看不出它具体落在哪一行代码上。直到我把每个模式拆开逐行去对才发现原则不是注释里写的标语而是代码结构本身。这篇博客把最常用的七个设计模式策略、装饰、代理、工厂方法、观察者、适配器、模板方法拆开告诉你每一行代码在守护哪条原则。代码注释里不会只扔一个OCP缩写而是写成(开闭原则) OCP这种形式降低阅读门槛。注本篇代码全部使用 Java 语言与软考官方教材保持一致。一、先统一暗号在看代码之前先把六大原则的中文名和英文缩写对号入座后面代码注释里都会写成中文 (英文缩写)的形式表格中文名英文缩写一句话记住它单一职责原则(单一职责) SRP一个类只负责一件事修改原因只有一个开闭原则(开闭原则) OCP新增功能时只新增文件不修改旧文件依赖倒转原则(依赖倒转) DIP变量声明用接口/抽象类不用具体类里氏替换原则(里氏替换) LSP子类能完全替代父类不改语义接口隔离原则(接口隔离) ISP接口要小而专别搞万能接口迪米特法则(迪米特法则) LoD只和直接朋友说话别搞a.b.c.doSomething()合成复用原则(合成复用) CRP优先用组合/has a别一上来就继承/is a二、策略模式Strategy—— 支付方式的插拔场景订单结算支持支付宝、微信、银行卡三种支付。未来可能还要加云闪付数字人民币。不用策略模式的写法反面教材// 这个类什么都管违反 (单一职责) SRP // 每次加新支付方式都要改这个文件违反 (开闭原则) OCP // 高层直接依赖具体实现违反 (依赖倒转) DIP public class OrderService { public void pay(String orderId, String payType) { if (payType.equals(Alipay)) { // 调用支付宝 SDK... } else if (payType.equals(Wechat)) { // 调用微信 SDK... } else if (payType.equals(Bank)) { // 调用银联 SDK... } } }策略模式的写法原则落地// 1. 抽象策略 —— (依赖倒转) DIP 的体现大家都依赖这个接口不依赖具体类 public interface PaymentStrategy { void pay(String orderId, double amount); } // 2. 具体策略 —— 每个类只负责一种支付体现 (单一职责) SRP public class AlipayStrategy implements PaymentStrategy { Override public void pay(String orderId, double amount) { // 只干支付宝的事别的不管 } } public class WechatStrategy implements PaymentStrategy { Override public void pay(String orderId, double amount) { // 只干微信的事别的不管 } } // 3. 上下文 —— 持有的是接口不是具体类体现 (依赖倒转) DIP public class OrderContext { private PaymentStrategy strategy; // ← (依赖倒转) DIP字段类型是接口 public OrderContext(PaymentStrategy strategy) { this.strategy strategy; // ← 通过构造注入不是 new 具体类 } public void checkout(String orderId, double amount) { strategy.pay(orderId, amount); // ← 运行时多态解耦 } } // 4. 使用 —— 新增云闪付时只需要新增一个类文件不改任何旧代码 // 这就是 (开闭原则) OCP对扩展开放对修改关闭 public class Client { public static void main(String[] args) { OrderContext order new OrderContext(new AlipayStrategy()); order.checkout(O2026001, 199.99); } }原则落点总结(单一职责) SRPAlipayStrategy只改支付宝逻辑OrderContext只负责调度流程各管一摊。(开闭原则) OCP新增支付方式 新增一个.java文件OrderContext一行不动。(依赖倒转) DIPOrderContext的字段类型是PaymentStrategy不是AlipayStrategy。三、装饰模式Decorator—— 给流套娃场景文件流需要叠加功能基础文件流 → 加缓冲 → 加加密 → 加压缩。每一层都是可选的顺序可以任意组合。代码实现// 1. 抽象组件 public abstract class Stream { public abstract void write(byte[] data); } // 2. 具体组件 —— 只负责真正写入磁盘体现 (单一职责) SRP public class FileStream extends Stream { Override public void write(byte[] data) { // 真正写入文件别的不管 } } // 3. 装饰基类 —— 关键持有组件接口而不是继承具体组件 // 这是 (合成复用) CRP 的体现用组合替代继承 public abstract class StreamDecorator extends Stream { protected Stream component; // ← (合成复用) CRPhas a Stream不是 is a Stream protected StreamDecorator(Stream component) { this.component component; } Override public void write(byte[] data) { component.write(data); // 委托给被装饰者 } } // 4. 具体装饰 —— 每个装饰只负责一个增强功能体现 (单一职责) SRP public class BufferedStream extends StreamDecorator { public BufferedStream(Stream stream) { super(stream); } Override public void write(byte[] data) { // 先缓冲自己的职责 super.write(data); // 再交给下层 } } public class EncryptedStream extends StreamDecorator { public EncryptedStream(Stream stream) { super(stream); } Override public void write(byte[] data) { // 先加密自己的职责 super.write(data); } } // 5. 使用 —— 运行时动态组合像套娃一样 // 体现 (开闭原则) OCP功能可以无限叠加不用改 FileStream 源码 public class Client { public static void main(String[] args) { Stream stream new FileStream(); stream new BufferedStream(stream); // 加缓冲 stream new EncryptedStream(stream); // 加加密 // 想加压缩再套一层stream new CompressedStream(stream); stream.write(new byte[]{}); } }原则落点总结(开闭原则) OCP要给流加压缩功能新建CompressedStream类即可FileStream、BufferedStream完全不用改。(合成复用) CRPBufferedStream不是extends FileStream而是内部has a Stream。如果继承缓冲流就只能装饰文件流组合则可以装饰网络流、内存流等任何Stream。(单一职责) SRPBufferedStream只关心缓冲EncryptedStream只关心加密绝不越界。四、代理模式Proxy—— 图片的门卫场景图片加载代理负责懒加载 权限检查 计数统计真实对象只管加载图片本身。代码实现// 1. 抽象主题 —— (依赖倒转) DIP 的抽象层 public interface Image { void display(); } // 2. 真实主题 —— (单一职责) SRP只负责从磁盘加载并显示不掺和权限、统计 public class RealImage implements Image { private String filename; public RealImage(String filename) { this.filename filename; loadFromDisk(); // 构造时就很重直接读磁盘 } private void loadFromDisk() { System.out.println(Loading filename); } Override public void display() { System.out.println(Displaying filename); } } // 3. 代理 —— (单一职责) SRP只负责控制访问不处理图片解码 public class ProxyImage implements Image { private RealImage realImage; // 组合关系 private String filename; public ProxyImage(String filename) { this.filename filename; // 构造时很轻不加载图片 } Override public void display() { // 权限检查代理自己的职责 if (!checkAccess()) return; // 懒加载代理自己的职责 if (realImage null) { realImage new RealImage(filename); } // 记录访问次数代理自己的职责 logAccess(); realImage.display(); // 最终委托给真实对象 } private boolean checkAccess() { /* ... */ return true; } private void logAccess() { /* ... */ } } // 4. 客户端 —— (迪米特法则) LoD客户端只认识代理不认识真实对象 // 客户端不知道 RealImage 的存在也不需要知道代理把真实对象藏起来了 public class Client { public static void main(String[] args) { Image image new ProxyImage(photo.jpg); image.display(); // 第一次会加载 image.display(); // 第二次直接显示不再加载 } }原则落点总结(单一职责) SRPRealImage只管图片解码ProxyImage只管访问控制、懒加载、日志。如果写在一个类里这个类就有两个修改原因图片格式变了 / 权限策略变了。(依赖倒转) DIP客户端声明Image image new ProxyImage(...)依赖的是接口不是具体类。(迪米特法则) LoD客户端只和ProxyImage打交道不和RealImage打交道更不用调用RealImage的内部方法。五、工厂方法模式Factory Method—— 把new关进笼子场景日志记录可能记录到文件、数据库、控制台。未来可能要加Redis日志。代码实现// 1. 产品接口 —— (依赖倒转) DIP 的抽象 public interface Logger { void log(String message); } // 2. 具体产品 —— (单一职责) SRP每个类只负责一种写入方式 public class FileLogger implements Logger { Override public void log(String message) { // 写入文件别的不管 // Java 中可用 FileWriter 等实现 } } public class DatabaseLogger implements Logger { Override public void log(String message) { // 写入数据库别的不管 } } // 3. 创建者抽象 —— 核心把创建对象的责任独立出来 public abstract class LoggerCreator { // 工厂方法由子类决定实例化哪个具体类 // 父类只依赖接口体现 (依赖倒转) DIP public abstract Logger createLogger(); // ← 延迟到子类实现 public void writeLog(String message) { Logger logger createLogger(); // ← 依赖抽象不是 new FileLogger() logger.log(message); } } // 4. 具体创建者 —— 每个创建者只负责创建一种日志器体现 (单一职责) SRP public class FileLoggerCreator extends LoggerCreator { Override public Logger createLogger() { return new FileLogger(); } } public class DatabaseLoggerCreator extends LoggerCreator { Override public Logger createLogger() { return new DatabaseLogger(); } } // 5. 使用 —— 要切换日志方式只需换创建者writeLog 方法完全不用改 // 新增Redis日志时新增 RedisLogger RedisLoggerCreator // 旧代码完全不动 —— 体现 (开闭原则) OCP public class Client { public static void main(String[] args) { LoggerCreator creator new FileLoggerCreator(); creator.writeLog(系统启动); // 实际写到文件 } }原则落点总结(开闭原则) OCP新增日志方式 新增两个类RedisLoggerRedisLoggerCreatorLoggerCreator的writeLog方法一行不改。(依赖倒转) DIPwriteLog里用的是Logger不是FileLoggercreateLogger返回的也是Logger。(单一职责) SRPFileLogger只管文件写入FileLoggerCreator只管创建LoggerCreator只管使用流程。如果不用工厂方法创建逻辑和使用逻辑会耦合在一个类里。六、观察者模式Observer—— 订阅与广播场景股票价格变动需要同时通知邮件告警系统短信通知系统APP推送系统。未来可能还要加微信机器人推送。代码实现import java.util.ArrayList; import java.util.List; // 1. 观察者接口 —— (依赖倒转) DIP主题只依赖这个接口不依赖具体观察者 public interface Observer { void update(String stockCode, double price); } // 2. 主题接口 —— 维护观察者列表提供订阅/取消订阅/通知 public interface Subject { void attach(Observer observer); void detach(Observer observer); void notifyObservers(); } // 3. 具体主题 —— 只负责维护股票数据和观察者列表体现 (单一职责) SRP public class Stock implements Subject { private ListObserver observers new ArrayList(); // ← 依赖接口 private String stockCode; private double price; public Stock(String stockCode, double price) { this.stockCode stockCode; this.price price; } public double getPrice() { return price; } public void setPrice(double price) { this.price price; notifyObservers(); // 价格一变就广播 } Override public void attach(Observer observer) { observers.add(observer); } Override public void detach(Observer observer) { observers.remove(observer); } Override public void notifyObservers() { for (Observer observer : observers) { observer.update(stockCode, price); // 统一调用接口方法 } } } // 4. 具体观察者 —— 每个观察者只负责一种通知方式体现 (单一职责) SRP public class EmailAlert implements Observer { Override public void update(String stockCode, double price) { System.out.println(邮件告警 stockCode 价格变为 price); } } public class SmsAlert implements Observer { Override public void update(String stockCode, double price) { System.out.println(短信通知 stockCode 价格变为 price); } } public class AppPush implements Observer { Override public void update(String stockCode, double price) { System.out.println(APP推送 stockCode 价格变为 price); } } // 5. 使用 —— 新增微信机器人推送新增 WeChatBot 类实现 Observer 即可 // Stock 类一行不改体现 (开闭原则) OCP public class Client { public static void main(String[] args) { Stock stock new Stock(000001, 10.5); stock.attach(new EmailAlert()); stock.attach(new SmsAlert()); stock.attach(new AppPush()); stock.setPrice(11.2); // 价格一变三个观察者同时收到通知 } }原则落点总结(开闭原则) OCP新增通知方式 新增一个实现Observer的类Stock类完全不用改。(依赖倒转) DIPStock内部的观察者列表是ListObserver不是ListEmailAlert。它只认识接口不认识具体观察者。(单一职责) SRPStock只管维护数据和通知流程EmailAlert只管发邮件SmsAlert只管发短信。各扫门前雪。七、适配器模式Adapter—— 旧接口的转接头场景老系统里有一个OldPrinter类接口是printOld()。新系统统一用Printer接口方法是print()。不能改老系统的源码怎么让老打印机接入新系统代码实现// 1. 新系统期望的接口 —— (依赖倒转) DIP 的目标接口 public interface Printer { void print(String content); } // 2. 老系统的类 —— 源码不能改可能是第三方库或遗留系统 public class OldPrinter { public void printOld(String content) { System.out.println(老打印机输出 content); } } // 3. 适配器 —— 把老接口包装成新接口体现 (开闭原则) OCP不改旧代码只新增适配器 public class OldPrinterAdapter implements Printer { private OldPrinter oldPrinter; // ← (合成复用) CRP组合旧对象不继承旧类 public OldPrinterAdapter(OldPrinter oldPrinter) { this.oldPrinter oldPrinter; } Override public void print(String content) { // 内部做接口转换新接口的 print 调用旧接口的 printOld oldPrinter.printOld(content); } } // 4. 新系统使用 —— 只认 Printer不认识 OldPrinter体现 (依赖倒转) DIP public class DocumentService { private Printer printer; // ← 依赖抽象接口 public DocumentService(Printer printer) { this.printer printer; } public void printDocument(String content) { printer.print(content); // 统一调用新接口 } } // 5. 使用 —— 老打印机通过适配器接入新系统OldPrinter 源码一行没动 public class Client { public static void main(String[] args) { OldPrinter old new OldPrinter(); Printer adapter new OldPrinterAdapter(old); DocumentService service new DocumentService(adapter); service.printDocument(年度报告); } }原则落点总结(开闭原则) OCPOldPrinter的源码不能改、也没改。新增OldPrinterAdapter就解决了兼容问题旧代码完全封闭。(合成复用) CRPOldPrinterAdapter不是extends OldPrinter可能也继承不了因为是第三方 final 类而是内部has a OldPrinter。(依赖倒转) DIPDocumentService只认Printer它不知道也不关心底层到底是新打印机还是老打印机加适配器。八、模板方法模式Template Method—— 骨架不变步骤可换场景数据导入流程读取文件 → 解析数据 → 校验数据 → 写入数据库。这个流程骨架是固定的但解析这一步CSV 和 Excel 的解析方式完全不同。代码实现import java.util.List; // 1. 抽象类 —— 定义算法骨架体现 (开闭原则) OCP骨架固定可变部分留给子类 public abstract class DataImporter { // 模板方法骨架final 不让子类乱改 public final void importData(String filePath) { String rawData readFile(filePath); // 通用步骤 ListDataRecord parsedData parse(rawData); // ← 可变步骤抽象方法子类实现 validate(parsedData); // 通用步骤 saveToDatabase(parsedData); // 通用步骤 } private String readFile(String filePath) { // Java 中可用 Files.readString() 等实现 return file content; // 具体实现子类不用管 } // 抽象方法 —— 子类必须实现体现 (依赖倒转) DIP父类依赖抽象方法不依赖具体解析逻辑 protected abstract ListDataRecord parse(String rawData); private void validate(ListDataRecord data) { // 通用校验逻辑 } private void saveToDatabase(ListDataRecord data) { // 通用写入逻辑 } } // 数据记录类辅助 class DataRecord { // 数据字段... } // 2. 具体子类 —— 只负责解析 CSV体现 (单一职责) SRP public class CsvImporter extends DataImporter { Override protected ListDataRecord parse(String rawData) { // 按逗号分割解析 CSV return parseCsv(rawData); } private ListDataRecord parseCsv(String rawData) { /* ... */ return null; } } // 3. 具体子类 —— 只负责解析 Excel体现 (单一职责) SRP public class ExcelImporter extends DataImporter { Override protected ListDataRecord parse(String rawData) { // 按单元格解析 Excel return parseExcel(rawData); } private ListDataRecord parseExcel(String rawData) { /* ... */ return null; } } // 4. 使用 —— 新增JSON 导入新建 JsonImporter 继承 DataImporter重写 parse 即可 // importData 方法的骨架一行不改体现 (开闭原则) OCP public class Client { public static void main(String[] args) { DataImporter importer new CsvImporter(); importer.importData(data.csv); } }原则落点总结(开闭原则) OCP新增一种导入格式比如 JSON只需要新增JsonImporter类并重写parse方法。DataImporter的importData骨架和readFile、validate、saveToDatabase完全不用改。(依赖倒转) DIPDataImporter作为父类它的importData方法调用的是抽象的parse方法而不是某个具体的解析逻辑。它依赖的是抽象步骤不是具体实现。(单一职责) SRPCsvImporter只懂 CSVExcelImporter只懂 Excel。解析逻辑和通用流程彻底分离。九、速查表模式 ↔ 原则带代码气味设计模式主要体现的原则代码里的识别气味策略模式(开闭原则) OCP、(依赖倒转) DIP、(单一职责) SRP接口作为参数传入构造器运行时切换算法装饰模式(开闭原则) OCP、(合成复用) CRP、(单一职责) SRP构造器传入同类接口方法里先干自己的事再super.write()委托代理模式(单一职责) SRP、(依赖倒转) DIP、(迪米特法则) LoD代理类内部new真实对象客户端只接触代理接口工厂方法(开闭原则) OCP、(依赖倒转) DIP、(单一职责) SRP把new语句隔离到子类工厂方法返回抽象类型观察者模式(开闭原则) OCP、(依赖倒转) DIP、(单一职责) SRP主题维护List接口遍历调用接口方法广播通知适配器模式(开闭原则) OCP、(合成复用) CRP、(依赖倒转) DIP包装旧接口内部做方法名/参数转换不改旧代码模板方法(开闭原则) OCP、(依赖倒转) DIP、(单一职责) SRP父类定义final骨架抽象方法留给子类填钩子十、一句话心法判断一段代码是否体现了某条原则不要看注释里写了什么只看三个地方成员变量声明的类型是接口还是具体类—— 这是 (依赖倒转) DIP新增功能时是新增文件还是修改旧文件—— 这是 (开闭原则) OCP类名和类里的方法是不是都在说同一件事—— 这是 (单一职责) SRP把这三个问题套到任何设计模式的代码上那些原本像标语一样的原则就会一行一行地钉在代码里看得见、摸得着。