【软考设计模式】简单工厂模式:多形态实现与代码填空精讲
系列定位:软考软件设计师 / 系统架构设计师 — 创建型模式专题第 1 讲
考察分值:上午题 1-2 分,下午题常结合代码填空出现
难度等级:⭐⭐☆☆☆(基础必拿分)
一、考纲定位与模式定义
1.1 考纲要求
简单工厂模式在软考中属于创建型模式的入门内容。考察形式包括:
-
上午选择题:判断描述所属模式;识别简单工厂与工厂方法的区别
-
下午设计题:补全工厂类的创建逻辑;根据类图写出工厂方法返回值
1.2 模式定义
简单工厂模式:定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。
核心意图:将对象的创建逻辑从客户端抽离,实现"创建与使用分离"。
二、UML 类图与角色划分
┌─────────────────────────────────────────────┐
│ Client │
│ + main() │
└─────────────────────┬───────────────────────┘
│ uses
▼
┌─────────────────────────────────────────────┐
│ SimpleFactory │
│ + createProduct(type): Product │
└─────────────────────┬───────────────────────┘
│ creates
┌───────────┴───────────┐
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ <<interface>> │ │ <<interface>> │
│ Product │◄────│ Product │
│ + operation() │ │ + operation() │
└─────────────────┘ └─────────────────┘
△ △
│ │
┌──────┴─────────┐ ┌──────┴─────────┐
│ │ │ │
┌──┴─────┐ ┌────┴───┐ ┌──┴─────┐ ┌────┴───┐
│Concrete│ │Concrete│ │Concrete│ │Concrete│
│ProductA│ │ProductB│ │ProductC│ │ProductD│
└────────┘ └────────┘ └────────┘ └────────┘
| 角色 | 职责 | 软考填空关键词 |
|---|---|---|
| Product | 抽象产品,定义接口 | interface / abstract class |
| ConcreteProduct | 具体产品,实现接口 | 实现类名 |
| SimpleFactory | 工厂类,负责创建实例 | create() / getInstance() |
| Client | 通过工厂获取产品 | 面向接口调用方法 |
三、场景一:文件解析器(接口 + 分支判断)
业务背景:系统需要解析 PDF、Excel、Word 三种文件格式,解析逻辑由工厂统一创建。
3.1 代码实现
// 抽象产品:接口
interface FileParser {
void parse(String filePath);
}
// 具体产品
class PdfParser implements FileParser {
public void parse(String filePath) {
System.out.println("解析 PDF 文件: " + filePath);
}
}
class ExcelParser implements FileParser {
public void parse(String filePath) {
System.out.println("解析 Excel 文件: " + filePath);
}
}
class WordParser implements FileParser {
public void parse(String filePath) {
System.out.println("解析 Word 文件: " + filePath);
}
}
// 工厂类:使用 if-else 分支判断
class ParserFactory {
public static FileParser createParser(String ext) {
if ("pdf".equalsIgnoreCase(ext)) {
return new PdfParser();
} else if ("excel".equalsIgnoreCase(ext) || "xlsx".equalsIgnoreCase(ext)) {
return new ExcelParser();
} else if ("word".equalsIgnoreCase(ext) || "docx".equalsIgnoreCase(ext)) {
return new WordParser();
}
throw new IllegalArgumentException("不支持的文件格式: " + ext);
}
}
// 客户端
public class ParserClient {
public static void main(String[] args) {
String extension = "pdf"; // 来自文件上传组件
FileParser parser = ParserFactory.createParser(extension);
parser.parse("/data/report.pdf");
}
}
四、场景二:缓存策略(抽象类 + Map 注册)
业务背景:系统支持内存缓存、Redis 缓存、磁盘缓存三种策略,通过 Map 预先注册,避免分支判断。
4.1 代码实现
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
// 抽象产品:抽象类(可含公共字段/方法)
abstract class Cache {
protected String name;
public Cache(String name) {
this.name = name;
}
public abstract void put(String key, Object value);
public abstract Object get(String key);
}
// 具体产品
class MemoryCache extends Cache {
public MemoryCache() { super("Memory"); }
public void put(String key, Object value) {
System.out.println("[" + name + "] 写入: " + key);
}
public Object get(String key) {
System.out.println("[" + name + "] 读取: " + key);
return null;
}
}
class RedisCache extends Cache {
public RedisCache() { super("Redis"); }
public void put(String key, Object value) {
System.out.println("[" + name + "] 写入: " + key);
}
public Object get(String key) {
System.out.println("[" + name + "] 读取: " + key);
return null;
}
}
class DiskCache extends Cache {
public DiskCache() { super("Disk"); }
public void put(String key, Object value) {
System.out.println("[" + name + "] 写入: " + key);
}
public Object get(String key) {
System.out.println("[" + name + "] 读取: " + key);
return null;
}
}
// 工厂类:使用 Map + Supplier 注册(Java 8+)
class CacheFactory {
private static final Map<String, Supplier<Cache>> registry = new HashMap<>();
// 静态注册
static {
registry.put("memory", MemoryCache::new);
registry.put("redis", RedisCache::new);
registry.put("disk", DiskCache::new);
}
public static Cache createCache(String type) {
Supplier<Cache> supplier = registry.get(type);
if (supplier == null) {
throw new IllegalArgumentException("未知缓存类型: " + type);
}
return supplier.get();
}
}
// 客户端
public class CacheClient {
public static void main(String[] args) {
Cache cache = CacheFactory.createCache("redis");
cache.put("user:1001", "张三");
}
}
五、场景三:数据加密(接口 + 配置文件反射)
业务背景:系统支持 AES、RSA、SM4 等加密算法,算法类名写在配置文件中,工厂通过反射动态创建,新增算法无需修改工厂代码。
5.1 代码实现
import java.io.InputStream;
import java.util.Properties;
// 抽象产品:接口
interface Encryptor {
String encrypt(String plainText);
String decrypt(String cipherText);
}
// 具体产品
class AesEncryptor implements Encryptor {
public String encrypt(String plainText) {
return "AES(" + plainText + ")";
}
public String decrypt(String cipherText) {
return "AES-decrypt(" + cipherText + ")";
}
}
class RsaEncryptor implements Encryptor {
public String encrypt(String plainText) {
return "RSA(" + plainText + ")";
}
public String decrypt(String cipherText) {
return "RSA-decrypt(" + cipherText + ")";
}
}
class Sm4Encryptor implements Encryptor {
public String encrypt(String plainText) {
return "SM4(" + plainText + ")";
}
public String decrypt(String cipherText) {
return "SM4-decrypt(" + cipherText + ")";
}
}
// 工厂类:通过配置文件 + 反射创建
class EncryptorFactory {
private static final Properties config = new Properties();
static {
try (InputStream is = EncryptorFactory.class
.getClassLoader()
.getResourceAsStream("encrypt.properties")) {
config.load(is);
} catch (Exception e) {
throw new RuntimeException("加载配置失败", e);
}
}
public static Encryptor createEncryptor(String name) {
String className = config.getProperty(name);
if (className == null) {
throw new IllegalArgumentException("未配置: " + name);
}
try {
Class<?> clazz = Class.forName(className);
return (Encryptor) clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("创建失败: " + className, e);
}
}
}
// 配置文件 encrypt.properties(放在 classpath 下)
// aes=com.example.AesEncryptor
// rsa=com.example.RsaEncryptor
// sm4=com.example.Sm4Encryptor
// 客户端
public class EncryptClient {
public static void main(String[] args) {
Encryptor encryptor = EncryptorFactory.createEncryptor("aes");
String result = encryptor.encrypt("敏感数据");
System.out.println(result);
}
}
六、三种实现方式对比
| 维度 | 场景一:分支判断 | 场景二:Map 注册 | 场景三:反射配置 |
|---|---|---|---|
| 抽象产品 | interface | abstract class | interface |
| 创建方式 | if-else / switch | Map<String, Supplier> | Class.forName() + 配置文件 |
| 扩展性 | 差(新增需改工厂) | 中(新增需改 Map 注册) | 优(新增只需改配置文件) |
| 软考适用 | 基础代码填空 | 考察 Java 8 特性 | 考察反射与配置化 |
| 复杂度 | 低 | 中 | 高 |
七、软考高频考点与易混淆辨析
7.1 高频考点
| 考点 | 内容 |
|---|---|
| 模式分类 | 创建型模式(非 GoF 23 正式成员,是工厂方法的简化) |
| 核心优点 | ① 客户端与具体产品解耦 ② 创建逻辑集中管理 |
| 核心缺点 | 违背开闭原则(OCP):新增产品需修改工厂类(场景一、二);场景三通过反射可缓解 |
| 返回值类型 | 必须是抽象产品(Product 接口或抽象类),不能是具体类 |
| 客户端编程 | 面向抽象产品编程,如 Product p = Factory.create("xxx") |
7.2 易混淆辨析:简单工厂 vs 工厂方法
| 对比项 | 简单工厂 | 工厂方法 |
|---|---|---|
| 工厂数量 | 一个工厂类 | 一个产品对应一个具体工厂 |
| 产品扩展 | 修改工厂类(违背 OCP) | 新增具体工厂类(符合 OCP) |
| 结构复杂度 | 简单 | 较复杂 |
| 软考出现 | 常作为代码填空基础题 | 常作为下午设计大题 |
八、真题风格模拟与代码填空
模拟题 1(上午选择题)
以下关于简单工厂模式的叙述中,正确的是()。
A. 简单工厂是 GoF 23 种设计模式之一
B. 简单工厂新增产品时,无需修改任何现有代码即可扩展
C. 简单工厂中,客户端应直接实例化具体产品类以提高效率
D. 简单工厂将对象的创建逻辑集中到工厂类,降低了客户端与具体产品的耦合
答案:D
解析:
-
A 错误:简单工厂不是 GoF 23 种正式模式,工厂方法才是。
-
B 错误:简单工厂新增产品通常需要修改工厂类的分支判断,违背开闭原则。
-
C 错误:客户端应面向抽象产品编程,通过工厂获取实例。
-
D 正确:这是简单工厂的核心优点。
模拟题 2(下午代码填空)
某系统需要支持多种数据导出格式(PDF、Excel),使用简单工厂模式实现。请补全(1)~(3)。
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);
}
}
class ExportFactory {
public static (1)______ createExport(String type) {
if ("pdf".equals(type)) {
return (2)______;
} else if ("excel".equals(type)) {
return (3)______;
}
return null;
}
}
public class Client {
public static void main(String[] args) {
ExportFile file = ExportFactory.createExport("pdf");
file.export("报表");
}
}
答案:
-
(1)
ExportFile -
(2)
new PdfExport() -
(3)
new ExcelExport()
九、常见陷阱与注意事项
陷阱 1:误认为简单工厂是 GoF 正式模式
简单工厂不在 GoF 23 种设计模式中。如果上午题问"以下属于 GoF 创建型模式的是",选项中出现"简单工厂"不能选。
陷阱 2:工厂返回值写成具体类
软考代码填空时,工厂方法的返回值类型必须是抽象产品(接口或抽象类),如 FileParser createParser(...) 而非 PdfParser createParser(...)。
陷阱 3:忽视空指针判断
若工厂返回 null,客户端直接调用方法会抛 NullPointerException。规范写法应在工厂内抛异常,或客户端做非空判断。
陷阱 4:switch 与 if-else 的等价性
无论工厂内用 switch 还是 if-else,软考阅卷均认可。但注意:Java 12+ 的 switch 表达式返回值更简洁,但软考通常按 Java 8 语法出题。
陷阱 5:与单例模式混淆
简单工厂可以返回单例对象(工厂内缓存实例),但简单工厂本身≠单例模式。不要看到"工厂只创建一个对象"就误判为单例。
十、总结
| 要点 | 内容 |
|---|---|
| 定义 | 一个工厂类根据参数创建不同实例,实例具有共同父类 |
| 分类 | 创建型模式(非 GoF 正式成员,工厂方法的简化版) |
| 实现形态 | 接口/抽象类 + 分支判断 / Map 注册 / 反射配置 |
| 优点 | 解耦、集中管理创建逻辑 |
| 缺点 | 违背开闭原则(反射配置可缓解) |
| 软考重点 | 与工厂方法/抽象工厂的辨析;代码填空补全工厂类;识别类图角色 |
| 答题技巧 | 看到"根据参数返回不同对象"且只有一个工厂类 → 选简单工厂 |

175

被折叠的 条评论
为什么被折叠?



