【软考设计模式】工厂方法模式:把实例化推迟到子类,代码填空精讲 系列定位软考软件设计师 / 系统架构设计师 — 创建型模式专题第 2 讲考察分值上午题 1-2 分下午题 15 分常作为面向对象设计大题核心模式难度等级⭐⭐⭐☆☆比简单工厂多一层抽象但逻辑更清晰一、考纲定位与模式定义1.1 考纲要求工厂方法模式在软考中属于创建型类模式GoF 23 种中唯一一个类创建型其余创建型均为对象创建型。考察形式包括上午选择题判断场景描述所属模式识别工厂方法与简单工厂、抽象工厂的区别下午设计题补全抽象工厂接口、具体工厂实现根据类图写出createProduct()方法的返回值1.2 模式定义工厂方法模式定义一个用于创建对象的接口让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。核心意图将创建谁的决定权从工厂类内部的分支判断下放给具体的工厂子类。每新增一种产品就新增一个工厂子类无需修改原有代码。二、UML 类图与角色划分┌─────────────────────────────────────────────────────┐ │ Client │ │ main() │ └───────────────────────┬─────────────────────────────┘ │ uses ▼ ┌─────────────────────────────────────────────────────┐ │ interface │ │ Creator抽象工厂 │ │ factoryMethod(): Product │ │ someOperation() │ └───────────────────────┬─────────────────────────────┘ △ implements ┌─────────────┴─────────────┐ │ │ ▼ ▼ ┌───────────────────┐ ┌───────────────────┐ │ ConcreteCreatorA │ │ ConcreteCreatorB │ │ factoryMethod() │ │ factoryMethod() │ │ return new │ │ return new │ │ ConcreteProductA│ │ ConcreteProductB│ └────────┬──────────┘ └────────┬──────────┘ │ creates │ creates ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ │ interface │ │ interface │ │ Product │◄──────────│ Product │ │ operation() │ │ operation() │ └─────────────────┘ └─────────────────┘ △ △ │ │ ┌──────┴─────────┐ ┌──────┴─────────┐ │ │ │ │ ┌──┴─────┐ ┌────┴───┐ ┌──┴─────┐ ┌────┴───┐ │Concrete│ │Concrete│ │Concrete│ │Concrete│ │ProductA│ │ProductB│ │ProductC│ │ProductD│ └────────┘ └────────┘ └────────┘ └────────┘角色职责软考填空关键词Product抽象产品定义接口interface/abstract classConcreteProduct具体产品实现接口实现类名Creator抽象工厂声明工厂方法abstract/interfacefactoryMethod()ConcreteCreator具体工厂实现工厂方法返回具体产品implements/extendsreturn new Xxx()Client面向 Creator 接口编程Creator creator new ConcreteCreator()三、场景一日志记录器最经典的工厂方法业务背景系统需要支持文件日志、数据库日志、控制台日志三种记录方式。每种日志对应一个工厂子类客户端通过统一的工厂接口获取日志记录器。3.1 代码实现// 抽象产品日志记录器接口 interface Logger { void write(String message); } // 具体产品 class FileLogger implements Logger { public void write(String message) { System.out.println([文件日志] message); } } class DatabaseLogger implements Logger { public void write(String message) { System.out.println([数据库日志] message); } } class ConsoleLogger implements Logger { public void write(String message) { System.out.println([控制台日志] message); } } // 抽象工厂声明工厂方法 interface LoggerFactory { Logger createLogger(); // 这就是工厂方法 } // 具体工厂每个工厂只负责创建一种日志记录器 class FileLoggerFactory implements LoggerFactory { public Logger createLogger() { return new FileLogger(); } } class DatabaseLoggerFactory implements LoggerFactory { public Logger createLogger() { return new DatabaseLogger(); } } class ConsoleLoggerFactory implements LoggerFactory { public Logger createLogger() { return new ConsoleLogger(); } } // 客户端 public class LoggerClient { public static void main(String[] args) { // 客户端面向抽象工厂编程不关心具体是哪个工厂 LoggerFactory factory new FileLoggerFactory(); Logger logger factory.createLogger(); logger.write(用户登录成功); // 想换数据库日志只需换工厂其余代码不动 LoggerFactory dbFactory new DatabaseLoggerFactory(); Logger dbLogger dbFactory.createLogger(); dbLogger.write(订单创建成功); } }关键体会新增一种日志比如 Kafka 日志只需新增KafkaLogger和KafkaLoggerFactory现有代码零修改。这就是工厂方法对开闭原则OCP的践行。四、场景二跨平台 UI 组件框架设计的典型用法业务背景设计一个跨平台 UI 框架需要支持 Windows 按钮和 Mac 按钮。框架层只定义抽象工厂和抽象产品具体平台实现由子类完成。说明这是 GoF 原书中工厂方法的经典例子也是软考下午题最爱考的框架扩展场景。4.1 代码实现// 抽象产品按钮 interface Button { void render(); void onClick(); } // 具体产品Windows 风格按钮 class WindowsButton implements Button { public void render() { System.out.println(渲染 Windows 风格按钮 ▯); } public void onClick() { System.out.println(Windows 按钮被点击); } } // 具体产品Mac 风格按钮 class MacButton implements Button { public void render() { System.out.println(渲染 Mac 风格按钮 ⬭); } public void onClick() { System.out.println(Mac 按钮被点击); } } // 抽象工厂对话框内含工厂方法 abstract class Dialog { // 工厂方法由子类决定创建哪种按钮 abstract Button createButton(); // 业务方法使用工厂方法创建的产品 public void show() { Button button createButton(); // 调用工厂方法 button.render(); button.onClick(); } } // 具体工厂Windows 对话框 class WindowsDialog extends Dialog { Override Button createButton() { return new WindowsButton(); } } // 具体工厂Mac 对话框 class MacDialog extends Dialog { Override Button createButton() { return new MacButton(); } } // 客户端 public class UIClient { public static void main(String[] args) { // 根据当前操作系统创建对应对话框 Dialog dialog new WindowsDialog(); dialog.show(); // 内部自动创建 WindowsButton Dialog macDialog new MacDialog(); macDialog.show(); // 内部自动创建 MacButton } }软考常考点这里的Dialog是抽象类既包含工厂方法createButton()也包含业务逻辑show()。工厂方法让框架层只知道要创建按钮但不知道具体创建哪种按钮。五、场景三数据库连接池带参数的工厂方法业务背景系统需要支持 MySQL 和 Oracle 连接池每种连接池的创建需要不同的初始化参数URL、用户名、密码。工厂方法可以带参数也可以结合配置文件实现。5.1 代码实现// 抽象产品数据库连接 interface DBConnection { void connect(); void execute(String sql); } // 具体产品 class MySQLConnection implements DBConnection { private String url; public MySQLConnection(String url, String user, String pwd) { this.url url; System.out.println(初始化 MySQL 连接: url); } public void connect() { System.out.println(MySQL 连接成功); } public void execute(String sql) { System.out.println(MySQL 执行: sql); } } class OracleConnection implements DBConnection { private String url; public OracleConnection(String url, String user, String pwd) { this.url url; System.out.println(初始化 Oracle 连接: url); } public void connect() { System.out.println(Oracle 连接成功); } public void execute(String sql) { System.out.println(Oracle 执行: sql); } } // 抽象工厂 interface DBConnectionFactory { DBConnection createConnection(String url, String user, String pwd); } // 具体工厂 class MySQLConnectionFactory implements DBConnectionFactory { public DBConnection createConnection(String url, String user, String pwd) { return new MySQLConnection(url, user, pwd); } } class OracleConnectionFactory implements DBConnectionFactory { public DBConnection createConnection(String url, String user, String pwd) { return new OracleConnection(url, user, pwd); } } // 客户端 public class DBClient { public static void main(String[] args) { // 通过配置文件或环境变量决定使用哪个工厂 String dbType mysql; // 可来自配置 DBConnectionFactory factory; if (mysql.equals(dbType)) { factory new MySQLConnectionFactory(); } else { factory new OracleConnectionFactory(); } DBConnection conn factory.createConnection( jdbc:mysql://localhost:3306/test, root, 123456 ); conn.connect(); conn.execute(SELECT * FROM users); } }六、三种场景对比与演进思路维度场景一日志场景二UI 框架场景三数据库抽象工厂类型interfaceabstract classinterface工厂方法参数无参无参有参配置信息产品复杂度简单对象简单对象带初始化参数的复杂对象典型应用替换实现类框架扩展跨平台/多环境适配软考考法下午代码填空下午类图补全下午参数传递七、软考高频考点与易混淆辨析7.1 高频考点考点内容模式分类创建型类模式GoF 23 中唯一类创建型核心优点① 符合开闭原则新增产品只需新增工厂子类② 将创建逻辑延迟到子类核心缺点每新增一个产品就要新增一个工厂类类数量翻倍工厂方法返回值必须是抽象产品类型如Product factoryMethod()与抽象工厂区别工厂方法一个工厂只创建一个产品抽象工厂一个工厂创建一族产品别名虚拟构造器Virtual Constructor7.2 易混淆辨析简单工厂 vs 工厂方法 vs 抽象工厂对比项简单工厂工厂方法抽象工厂GoF 23种❌ 否✅ 是✅ 是工厂数量1 个1 产品对应 1 个1 产品族对应 1 个产品数量多种同等级1 种多种相关族新增产品改工厂类新增工厂子类新增工厂子类 改接口开闭原则违背符合新增产品族符合新增产品种类违背软考难度基础填空下午大题下午大题最难八、真题风格模拟与代码填空模拟题 1上午选择题以下关于工厂方法模式的叙述中正确的是。A. 工厂方法模式属于结构型模式B. 工厂方法模式中客户端直接实例化具体产品类C. 工厂方法模式将产品的实例化延迟到其子类完成D. 工厂方法模式要求一个工厂类负责创建所有产品答案C解析A 错误工厂方法属于创建型模式不是结构型。B 错误客户端面向抽象工厂/抽象产品编程不直接new具体产品。C 正确这是工厂方法模式的核心定义。D 错误这是简单工厂的特点工厂方法是一个产品对应一个工厂子类。模拟题 2下午代码填空某系统需要支持多种数据导出格式PDF、Excel使用工厂方法模式实现。请补全1~4。interface ExportFile { void export(String data); } class PdfExport implements ExportFile { public void export(String data) { System.out.println(PDF: data); } } class ExcelExport implements ExportFile { public void export(String data) { System.out.println(Excel: data); } } // 抽象工厂 interface ExportFactory { (1)______ createExport(); } // PDF 工厂 class PdfFactory implements ExportFactory { public (2)______ createExport() { return (3)______; } } // Excel 工厂 class ExcelFactory implements ExportFactory { public ExportFile createExport() { return (4)______; } } public class Client { public static void main(String[] args) { ExportFactory factory new PdfFactory(); ExportFile file factory.createExport(); file.export(报表); } }答案(1)ExportFile(2)ExportFile(3)new PdfExport()(4)new ExcelExport()阅卷要点(1)(2) 必须填抽象产品ExportFile不能填具体类。这是工厂方法模式的硬性要求。九、常见陷阱与注意事项陷阱 1把工厂方法写成简单工厂软考代码填空里如果看到工厂类里有if-else或switch分支判断那是简单工厂不是工厂方法。工厂方法的每个具体工厂只负责创建一种产品没有分支。陷阱 2抽象工厂用interface还是abstract class都可以。GoF 原书中用的是抽象类因为抽象类可以包含默认业务逻辑如场景二的show()方法。软考中两种写法都算对但注意如果工厂方法需要与其他业务方法组合用abstract class更自然。陷阱 3与抽象工厂混淆工厂方法 一个工厂创建一个产品。抽象工厂 一个工厂创建一族相关产品如同时创建按钮和文本框。软考下午题如果类图里只有一个create()方法是工厂方法如果有多个createXxx()方法是抽象工厂。陷阱 4工厂方法可以带参数工厂方法的参数通常用于传递初始化数据如场景三的数据库连接配置但这些参数不用于决定创建哪种产品那是简单工厂的做法。决定创建哪种产品的是具体工厂子类的选择。十、总结要点内容定义定义创建接口让子类决定实例化哪个类分类创建型类模式GoF 23 正式成员核心角色抽象产品、具体产品、抽象工厂Creator、具体工厂核心优点符合开闭原则新增产品无需修改现有代码核心缺点类数量膨胀产品类 工厂类成对增加与简单工厂区别简单工厂一个工厂类包打天下工厂方法一个产品一个工厂子类与抽象工厂区别工厂方法创建单个产品抽象工厂创建产品族软考重点下午代码填空补全工厂方法返回值识别类图角色与简单工厂/抽象工厂辨析答题技巧看到子类决定实例化哪个类→ 工厂方法看到一族相关产品→ 抽象工厂系列预告下一篇将讲单例模式—— 创建型模式中最简单、最常用、但面试和软考陷阱最多的模式。饿汉懒汉、双重检查锁、枚举单例咱们逐一拆解。