设计模式系列(08):创建型模式 - 原型模式

发布于:2025-06-28 ⋅ 阅读:(22) ⋅ 点赞:(0)

系列导读:完成创建型模式的学习,我们来看最后一个创建型模式——原型模式。它通过复制已有对象来创建新对象,是一种独特的创建方式。

解决什么问题:通过复制现有对象来创建新对象,而不是重新实例化。适用于对象创建成本高、需要保持状态的场景。

在实际开发中,有时候创建一个对象的成本很高,比如需要从数据库查询大量数据、进行复杂计算、或者建立网络连接等。如果需要创建多个相似的对象,每次都重新执行这些操作就太浪费了。

原型模式提供了一个聪明的解决方案:先创建一个原型对象,然后通过复制这个原型来创建新对象。这样既保留了对象的状态,又避免了重复的创建成本。

本文在系列中的位置


目录

1. 模式概述

原型模式(Prototype Pattern)是一种创建型设计模式。它通过克隆现有对象来创建新对象,而不是通过实例化类来创建。适用于对象创建成本高、状态复杂或需批量复制的场景。

1.1 定义

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

1.2 目的

  • 对象复制,提升效率
  • 避免子类膨胀,简化扩展
  • 支持动态配置和运行时扩展

2. 使用场景

原型模式在实际开发中应用广泛,常见场景包括:

  1. 文档/模板复制:如Word、PPT、设计稿等模板批量生成。
  2. 游戏开发:如角色、道具、场景等对象池批量克隆。
  3. 缓存/对象池:如数据库连接、线程、任务等对象池管理。
  4. 配置/环境克隆:如系统配置、环境参数的快速复制。

真实业务背景举例:

  • OA系统支持一键复制审批单、合同、报表等,原型模式可高效实现。
  • 游戏引擎批量生成怪物、NPC、道具等,原型模式提升性能。
  • 云平台环境模板、配置模板的快速克隆。

3. 优缺点分析

3.1 优点

  1. 性能优化:减少重复创建开销,提升系统响应速度。
  2. 简化扩展:无需大量子类,支持动态扩展和运行时配置。
  3. 灵活性高:可动态注册、批量复制,适应多变需求。

3.2 缺点

  1. 深拷贝复杂:对象关系复杂时,深拷贝实现难度大。
  2. 克隆限制:如不可变对象、资源句柄等难以克隆。
  3. 维护成本:需保证克隆对象状态一致性,易出错。

4. 实际应用案例

  1. 文档/模板复制:如Word、PPT、设计稿等模板批量生成。
  2. 游戏开发:如角色、道具、场景等对象池批量克隆。
  3. 缓存/对象池:如数据库连接、线程、任务等对象池管理。
  4. 配置/环境克隆:如系统配置、环境参数的快速复制。

5. 结构与UML类图

@startuml
package "Prototype Pattern" #DDDDDD {
  interface Prototype {
    + clone(): Prototype
  }
  class ConcretePrototype implements Prototype {
    - field: String
    + clone(): Prototype
  }
  class PrototypeRegistry {
    + addPrototype(key: String, prototype: Prototype): void
    + getPrototype(key: String): Prototype
    - prototypes: Map<String, Prototype>
  }
  Prototype <|.. ConcretePrototype
  PrototypeRegistry o-- Prototype : prototypes
}
@enduml

6. 代码示例

6.1 基本结构示例

业务背景: 实现原型模式的基本结构,支持对象克隆和注册表管理。

package com.example.patterns.prototype;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

// 原型接口,定义克隆方法
public interface Prototype {
    /**
     * 克隆当前对象
     * @return 克隆后的新对象
     */
    Prototype clone();
}

// 具体原型实现,支持基本属性克隆
public class ConcretePrototype implements Prototype {
    private String field;
    private int value;
    
    public ConcretePrototype(String field, int value) { 
        this.field = field;
        this.value = value;
    }
    
    // 拷贝构造函数,用于克隆
    private ConcretePrototype(ConcretePrototype other) {
        this.field = other.field;
        this.value = other.value;
    }
    
    @Override
    public Prototype clone() { 
        return new ConcretePrototype(this); 
    }
    
    // Getter和Setter方法
    public String getField() { return field; }
    public void setField(String field) { this.field = field; }
    public int getValue() { return value; }
    public void setValue(int value) { this.value = value; }
    
    @Override
    public String toString() {
        return "ConcretePrototype{field='" + field + "', value=" + value + "}";
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ConcretePrototype that = (ConcretePrototype) o;
        return value == that.value && Objects.equals(field, that.field);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(field, value);
    }
}

// 原型注册表,管理原型对象
public class PrototypeRegistry {
    private static final Map<String, Prototype> prototypes = new HashMap<>();
    
    /**
     * 注册原型对象
     */
    public static void addPrototype(String key, Prototype prototype) {
        if (key == null || prototype == null) {
            throw new IllegalArgumentException("Key and prototype cannot be null");
        }
        prototypes.put(key, prototype);
    }
    
    /**
     * 获取原型的克隆对象
     */
    public static Prototype getPrototype(String key) {
        Prototype prototype = prototypes.get(key);
        if (prototype == null) {
            throw new IllegalArgumentException("Prototype not found for key: " + key);
        }
        return prototype.clone();
    }
    
    /**
     * 移除原型
     */
    public static void removePrototype(String key) {
        prototypes.remove(key);
    }
    
    /**
     * 清空所有原型
     */
    public static void clear() {
        prototypes.clear();
    }
}

6.2 深拷贝实现示例

业务背景: 实现包含复杂对象的深拷贝,避免引用共享问题。

// 复杂对象示例:员工信息包含地址对象
public class Address implements Cloneable {
    private String city;
    private String street;
    private String zipCode;
    
    public Address(String city, String street, String zipCode) {
        this.city = city;
        this.street = street;
        this.zipCode = zipCode;
    }
    
    // 深拷贝实现
    @Override
    public Address clone() {
        try {
            return (Address) super.clone();
        } catch (CloneNotSupportedException e) {
            // 这种情况不应该发生,因为我们实现了Cloneable
            throw new RuntimeException("Clone not supported", e);
        }
    }
    
    // Getter和Setter方法
    public String getCity() { return city; }
    public void setCity(String city) { this.city = city; }
    public String getStreet() { return street; }
    public void setStreet(String street) { this.street = street; }
    public String getZipCode() { return zipCode; }
    public void setZipCode(String zipCode) { this.zipCode = zipCode; }
    
    @Override
    public String toString() {
        return "Address{city='" + city + "', street='" + street + "', zipCode='" + zipCode + "'}";
    }
}

// 员工原型,包含复杂对象引用
public class Employee implements Prototype {
    private String name;
    private String department;
    private Address address;
    private List<String> skills;
    
    public Employee(String name, String department, Address address) {
        this.name = name;
        this.department = department;
        this.address = address;
        this.skills = new ArrayList<>();
    }
    
    // 深拷贝构造函数
    private Employee(Employee other) {
        this.name = other.name;
        this.department = other.department;
        // 深拷贝地址对象
        this.address = other.address != null ? other.address.clone() : null;
        // 深拷贝技能列表
        this.skills = new ArrayList<>(other.skills);
    }
    
    @Override
    public Prototype clone() {
        return new Employee(this);
    }
    
    public void addSkill(String skill) {
        skills.add(skill);
    }
    
    // Getter和Setter方法
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getDepartment() { return department; }
    public void setDepartment(String department) { this.department = department; }
    public Address getAddress() { return address; }
    public void setAddress(Address address) { this.address = address; }
    public List<String> getSkills() { return skills; }
    
    @Override
    public String toString() {
        return "Employee{name='" + name + "', department='" + department + 
               "', address=" + address + ", skills=" + skills + "}";
    }
}

6.3 序列化克隆实现

业务背景: 使用序列化方式实现深拷贝,适用于复杂对象图。

import java.io.*;

// 支持序列化克隆的基类
public abstract class SerializablePrototype implements Prototype, Serializable {
    private static final long serialVersionUID = 1L;
    
    @Override
    public Prototype clone() {
        try {
            // 使用序列化进行深拷贝
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            oos.close();
            
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            Prototype cloned = (Prototype) ois.readObject();
            ois.close();
            
            return cloned;
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException("Serialization clone failed", e);
        }
    }
}

// 使用序列化克隆的配置对象
public class Configuration extends SerializablePrototype {
    private String environment;
    private Map<String, String> properties;
    private List<String> servers;
    
    public Configuration(String environment) {
        this.environment = environment;
        this.properties = new HashMap<>();
        this.servers = new ArrayList<>();
    }
    
    public void addProperty(String key, String value) {
        properties.put(key, value);
    }
    
    public void addServer(String server) {
        servers.add(server);
    }
    
    // Getter方法
    public String getEnvironment() { return environment; }
    public Map<String, String> getProperties() { return properties; }
    public List<String> getServers() { return servers; }
    
    @Override
    public String toString() {
        return "Configuration{environment='" + environment + 
               "', properties=" + properties + ", servers=" + servers + "}";
    }
}

6.4 实际业务场景:文档模板系统

业务背景: 实现文档模板系统,支持快速复制和定制不同类型的文档。

// 文档接口
public interface Document extends Prototype {
    void setTitle(String title);
    void setContent(String content);
    String getTitle();
    String getContent();
}

// 报告文档原型
public class ReportDocument implements Document {
    private String title;
    private String content;
    private String author;
    private Date createDate;
    private List<String> sections;
    
    public ReportDocument() {
        this.createDate = new Date();
        this.sections = new ArrayList<>();
        // 预设报告模板结构
        this.sections.add("摘要");
        this.sections.add("背景");
        this.sections.add("分析");
        this.sections.add("结论");
    }
    
    // 拷贝构造函数
    private ReportDocument(ReportDocument other) {
        this.title = other.title;
        this.content = other.content;
        this.author = other.author;
        this.createDate = new Date(); // 新文档使用当前时间
        this.sections = new ArrayList<>(other.sections);
    }
    
    @Override
    public Prototype clone() {
        return new ReportDocument(this);
    }
    
    @Override
    public void setTitle(String title) { this.title = title; }
    @Override
    public void setContent(String content) { this.content = content; }
    @Override
    public String getTitle() { return title; }
    @Override
    public String getContent() { return content; }
    
    public void setAuthor(String author) { this.author = author; }
    public String getAuthor() { return author; }
    public List<String> getSections() { return sections; }
    
    @Override
    public String toString() {
        return "ReportDocument{title='" + title + "', author='" + author + 
               "', sections=" + sections.size() + ", createDate=" + createDate + "}";
    }
}

// 合同文档原型
public class ContractDocument implements Document {
    private String title;
    private String content;
    private String partyA;
    private String partyB;
    private Date effectiveDate;
    private BigDecimal amount;
    
    public ContractDocument() {
        this.effectiveDate = new Date();
        this.amount = BigDecimal.ZERO;
    }
    
    private ContractDocument(ContractDocument other) {
        this.title = other.title;
        this.content = other.content;
        this.partyA = other.partyA;
        this.partyB = other.partyB;
        this.effectiveDate = new Date(); // 新合同使用当前日期
        this.amount = other.amount;
    }
    
    @Override
    public Prototype clone() {
        return new ContractDocument(this);
    }
    
    @Override
    public void setTitle(String title) { this.title = title; }
    @Override
    public void setContent(String content) { this.content = content; }
    @Override
    public String getTitle() { return title; }
    @Override
    public String getContent() { return content; }
    
    // 合同特有方法
    public void setPartyA(String partyA) { this.partyA = partyA; }
    public void setPartyB(String partyB) { this.partyB = partyB; }
    public void setAmount(BigDecimal amount) { this.amount = amount; }
    
    @Override
    public String toString() {
        return "ContractDocument{title='" + title + "', partyA='" + partyA + 
               "', partyB='" + partyB + "', amount=" + amount + "}";
    }
}

// 文档管理器
public class DocumentManager {
    private static final PrototypeRegistry registry = new PrototypeRegistry();
    
    static {
        // 初始化文档模板
        ReportDocument reportTemplate = new ReportDocument();
        reportTemplate.setTitle("月度报告模板");
        reportTemplate.setAuthor("系统管理员");
        
        ContractDocument contractTemplate = new ContractDocument();
        contractTemplate.setTitle("标准合同模板");
        contractTemplate.setPartyA("甲方公司");
        
        registry.addPrototype("report", reportTemplate);
        registry.addPrototype("contract", contractTemplate);
    }
    
    public static Document createDocument(String type, String title) {
        Document doc = (Document) registry.getPrototype(type);
        doc.setTitle(title);
        return doc;
    }
}

// 客户端使用示例
public class DocumentClient {
    public static void main(String[] args) {
        // 创建报告文档
        Document report1 = DocumentManager.createDocument("report", "Q1财务报告");
        report1.setContent("第一季度财务分析内容...");
        
        Document report2 = DocumentManager.createDocument("report", "Q2财务报告");
        report2.setContent("第二季度财务分析内容...");
        
        // 创建合同文档
        Document contract1 = DocumentManager.createDocument("contract", "软件开发合同");
        contract1.setContent("软件开发合同条款...");
        
        System.out.println("Report 1: " + report1);
        System.out.println("Report 2: " + report2);
        System.out.println("Contract: " + contract1);
    }
    // 总结:通过原型模式,文档系统可快速创建各种类型文档,提升效率和一致性。
}

## 7. 测试用例

**业务背景:** 验证原型模式的核心功能,包括基本克隆、深拷贝和注册表管理。

```java
import org.junit.Test;
import static org.junit.Assert.*;

public class PrototypePatternTest {
    
    @Test
    public void testBasicClone() {
        // 测试基本克隆功能
        ConcretePrototype original = new ConcretePrototype("test", 42);
        ConcretePrototype cloned = (ConcretePrototype) original.clone();
        
        // 验证克隆对象与原对象不是同一个实例
        assertNotSame(original, cloned);
        // 验证克隆对象的内容相同
        assertEquals(original.getField(), cloned.getField());
        assertEquals(original.getValue(), cloned.getValue());
        assertEquals(original, cloned);
    }
    
    @Test
    public void testPrototypeRegistry() {
        // 清空注册表
        PrototypeRegistry.clear();
        
        // 注册原型
        ConcretePrototype prototype = new ConcretePrototype("template", 100);
        PrototypeRegistry.addPrototype("test", prototype);
        
        // 获取克隆对象
        Prototype cloned = PrototypeRegistry.getPrototype("test");
        ConcretePrototype clonedConcrete = (ConcretePrototype) cloned;
        
        assertEquals("template", clonedConcrete.getField());
        assertEquals(100, clonedConcrete.getValue());
        assertNotSame(prototype, cloned);
    }
    
    @Test
    public void testDeepCopy() {
        // 测试深拷贝
        Address address = new Address("北京", "中关村大街", "100080");
        Employee original = new Employee("张三", "技术部", address);
        original.addSkill("Java");
        original.addSkill("Spring");
        
        Employee cloned = (Employee) original.clone();
        
        // 验证深拷贝:修改克隆对象的地址不影响原对象
        cloned.getAddress().setCity("上海");
        cloned.addSkill("Python");
        
        assertEquals("北京", original.getAddress().getCity());
        assertEquals("上海", cloned.getAddress().getCity());
        assertEquals(2, original.getSkills().size());
        assertEquals(3, cloned.getSkills().size());
    }
    
    @Test
    public void testDocumentSystem() {
        // 测试文档模板系统
        Document report1 = DocumentManager.createDocument("report", "Q1报告");
        Document report2 = DocumentManager.createDocument("report", "Q2报告");
        
        // 验证不同的文档实例
        assertNotSame(report1, report2);
        assertEquals("Q1报告", report1.getTitle());
        assertEquals("Q2报告", report2.getTitle());
        
        // 验证报告文档的特定属性
        assertTrue(report1 instanceof ReportDocument);
        assertTrue(report2 instanceof ReportDocument);
    }
    
    @Test
    public void testRegistryExceptionHandling() {
        // 测试注册表异常处理
        assertThrows(IllegalArgumentException.class, () -> {
            PrototypeRegistry.addPrototype(null, new ConcretePrototype("test", 1));
        });
        
        assertThrows(IllegalArgumentException.class, () -> {
            PrototypeRegistry.addPrototype("test", null);
        });
        
        assertThrows(IllegalArgumentException.class, () -> {
            PrototypeRegistry.getPrototype("nonexistent");
        });
    }
}

8. 常见误区与反例

8.1 常见误区

  • 误区1 :浅拷贝导致引用共享

    // 错误示例:简单的字段复制导致引用共享
    public class BadEmployee implements Prototype {
        private Address address;
        public Prototype clone() {
            BadEmployee clone = new BadEmployee();
            clone.address = this.address; // 浅拷贝,共享引用
            return clone;
        }
    }
    

    正确做法:实现深拷贝,确保引用对象也被克隆。

  • 误区2 :原型注册表未做防护

    // 错误示例:直接返回原型对象
    public static Prototype getPrototype(String key) {
        return prototypes.get(key); // 返回原始对象,可能被修改
    }
    

    正确做法:始终返回克隆对象,保护原型不被修改。

  • 误区3 :忽略克隆中的资源管理

    // 错误示例:资源句柄直接克隆
    public class BadResource implements Prototype {
        private FileInputStream inputStream;
        public Prototype clone() {
            BadResource clone = new BadResource();
            clone.inputStream = this.inputStream; // 资源不能共享
            return clone;
        }
    }
    

8.2 反例分析

  • 反例1 :不可变对象使用原型模式
    对于StringInteger等不可变对象,使用原型模式没有意义,应直接重用。

  • 反例2 :系统资源句柄克隆
    文件句柄、网络连接、数据库连接等系统资源不能简单克隆,需要重新创建。

  • 反例3 :过度使用原型模式
    对于简单对象,直接使用构造函数创建更简单高效,不需要原型模式。

9. 最佳实践

9.1 设计原则

  1. 深拷贝实现 :优先实现深拷贝,避免引用共享问题

    // 推荐:使用拷贝构造函数
    private Employee(Employee other) {
        this.name = other.name;
        this.address = other.address != null ? other.address.clone() : null;
        this.skills = new ArrayList<>(other.skills);
    }
    
  2. 注册表防护 :注册表应返回克隆对象,保护原型安全

    // 推荐:防护性克隆
    public static Prototype getPrototype(String key) {
        Prototype prototype = prototypes.get(key);
        return prototype != null ? prototype.clone() : null;
    }
    
  3. 异常与资源管理 :妥善处理克隆过程中的异常和资源

    // 推荐:完善的异常处理
    @Override
    public Prototype clone() {
        try {
            MyClass cloned = (MyClass) super.clone();
            cloned.resource = createNewResource(); // 重新创建资源
            return cloned;
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException("Clone failed", e);
        }
    }
    

9.2 性能优化

  1. 克隆策略选择 :根据对象复杂度选择合适的克隆策略

    • 简单对象:拷贝构造函数
    • 复杂对象:序列化克隆
    • 不可变部分:引用复制
  2. 延迟克隆 :对于大对象,考虑写时复制(Copy-on-Write)策略

    // 推荐:写时复制优化
    public class LazyCloneList implements Prototype {
        private List<String> data;
        private boolean isCloned = false;
        
        private void ensureCloned() {
            if (!isCloned) {
                data = new ArrayList<>(data);
                isCloned = true;
            }
        }
        
        public void add(String item) {
            ensureCloned();
            data.add(item);
        }
    }
    

9.3 架构设计

  1. 与工厂模式结合 :原型注册表可与工厂模式结合,提供统一的对象创建接口

  2. 线程安全考虑 :在多线程环境下,确保原型注册表和克隆操作的线程安全

    // 推荐:线程安全的注册表
    public class ThreadSafeRegistry {
        private static final ConcurrentHashMap<String, Prototype> prototypes = 
            new ConcurrentHashMap<>();
    }
    
  3. 版本控制 :为原型对象添加版本信息,支持向后兼容的序列化克隆

10. 参考资料与延伸阅读

  • 《设计模式:可复用面向对象软件的基础》GoF
  • Effective Java(中文版)
  • https://refactoringguru.cn/design-patterns/prototype
  • https://www.baeldung.com/java-prototype-pattern

本文为设计模式系列第8篇,后续每篇将聚焦一个设计模式或设计原则,深入讲解实现与应用,敬请关注。


网站公告

今日签到

点亮在社区的每一天
去签到