【软考设计模式】简单工厂模式:多形态实现与代码填空精讲

【软考设计模式】简单工厂模式:多形态实现与代码填空精讲

系列定位 :软考软件设计师 / 系统架构设计师 --- 创建型模式专题第 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 代码实现

java 复制代码
// 抽象产品:接口
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 代码实现

java 复制代码
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 代码实现

java 复制代码
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)。

java 复制代码
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 注册 / 反射配置
优点 解耦、集中管理创建逻辑
缺点 违背开闭原则(反射配置可缓解)
软考重点 与工厂方法/抽象工厂的辨析;代码填空补全工厂类;识别类图角色
答题技巧 看到"根据参数返回不同对象"且只有一个工厂类 → 选简单工厂