系列导读:完成创建型模式的学习,我们来看最后一个创建型模式——原型模式。它通过复制已有对象来创建新对象,是一种独特的创建方式。
解决什么问题:通过复制现有对象来创建新对象,而不是重新实例化。适用于对象创建成本高、需要保持状态的场景。
在实际开发中,有时候创建一个对象的成本很高,比如需要从数据库查询大量数据、进行复杂计算、或者建立网络连接等。如果需要创建多个相似的对象,每次都重新执行这些操作就太浪费了。
原型模式提供了一个聪明的解决方案:先创建一个原型对象,然后通过复制这个原型来创建新对象。这样既保留了对象的状态,又避免了重复的创建成本。
本文在系列中的位置:
- 前置知识:建造者模式
- 系列角色:创建型模式收尾
- 难度等级:★★★☆☆(需要理解深拷贝和浅拷贝)
- 后续学习:结构型模式:适配器模式
目录
- 1. 模式概述
- 2. 使用场景
- 3. 优缺点分析
- 4. 实际应用案例
- 5. 结构与UML类图
- 6. 代码示例
- 7. 测试用例
- 8. 常见误区与反例
- 9. 最佳实践
- 10. 参考资料与延伸阅读
1. 模式概述
原型模式(Prototype Pattern)是一种创建型设计模式。它通过克隆现有对象来创建新对象,而不是通过实例化类来创建。适用于对象创建成本高、状态复杂或需批量复制的场景。
1.1 定义
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
1.2 目的
- 对象复制,提升效率
- 避免子类膨胀,简化扩展
- 支持动态配置和运行时扩展
2. 使用场景
原型模式在实际开发中应用广泛,常见场景包括:
- 文档/模板复制:如Word、PPT、设计稿等模板批量生成。
- 游戏开发:如角色、道具、场景等对象池批量克隆。
- 缓存/对象池:如数据库连接、线程、任务等对象池管理。
- 配置/环境克隆:如系统配置、环境参数的快速复制。
真实业务背景举例:
- OA系统支持一键复制审批单、合同、报表等,原型模式可高效实现。
- 游戏引擎批量生成怪物、NPC、道具等,原型模式提升性能。
- 云平台环境模板、配置模板的快速克隆。
3. 优缺点分析
3.1 优点
- 性能优化:减少重复创建开销,提升系统响应速度。
- 简化扩展:无需大量子类,支持动态扩展和运行时配置。
- 灵活性高:可动态注册、批量复制,适应多变需求。
3.2 缺点
- 深拷贝复杂:对象关系复杂时,深拷贝实现难度大。
- 克隆限制:如不可变对象、资源句柄等难以克隆。
- 维护成本:需保证克隆对象状态一致性,易出错。
4. 实际应用案例
- 文档/模板复制:如Word、PPT、设计稿等模板批量生成。
- 游戏开发:如角色、道具、场景等对象池批量克隆。
- 缓存/对象池:如数据库连接、线程、任务等对象池管理。
- 配置/环境克隆:如系统配置、环境参数的快速复制。
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 <|.. ConcretePrototypePrototypeRegistry 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;}@Overridepublic 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; }@Overridepublic String toString() {return "ConcretePrototype{field='" + field + "', value=" + value + "}";}@Overridepublic 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);}@Overridepublic 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;}// 深拷贝实现@Overridepublic Address clone() {try {return (Address) super.clone();} catch (CloneNotSupportedException e) {// 这种情况不应该发生,因为我们实现了Cloneablethrow 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; }@Overridepublic 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);}@Overridepublic 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; }@Overridepublic 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;@Overridepublic 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; }@Overridepublic 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);}@Overridepublic Prototype clone() {return new ReportDocument(this);}@Overridepublic void setTitle(String title) { this.title = title; }@Overridepublic void setContent(String content) { this.content = content; }@Overridepublic String getTitle() { return title; }@Overridepublic String getContent() { return content; }public void setAuthor(String author) { this.author = author; }public String getAuthor() { return author; }public List<String> getSections() { return sections; }@Overridepublic 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;}@Overridepublic Prototype clone() {return new ContractDocument(this);}@Overridepublic void setTitle(String title) { this.title = title; }@Overridepublic void setContent(String content) { this.content = content; }@Overridepublic String getTitle() { return title; }@Overridepublic 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; }@Overridepublic 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 {@Testpublic 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);}@Testpublic 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);}@Testpublic 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());}@Testpublic 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);}@Testpublic 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 :不可变对象使用原型模式
对于String
、Integer
等不可变对象,使用原型模式没有意义,应直接重用。 -
反例2 :系统资源句柄克隆
文件句柄、网络连接、数据库连接等系统资源不能简单克隆,需要重新创建。 -
反例3 :过度使用原型模式
对于简单对象,直接使用构造函数创建更简单高效,不需要原型模式。
9. 最佳实践
9.1 设计原则
-
深拷贝实现 :优先实现深拷贝,避免引用共享问题
// 推荐:使用拷贝构造函数 private Employee(Employee other) {this.name = other.name;this.address = other.address != null ? other.address.clone() : null;this.skills = new ArrayList<>(other.skills); }
-
注册表防护 :注册表应返回克隆对象,保护原型安全
// 推荐:防护性克隆 public static Prototype getPrototype(String key) {Prototype prototype = prototypes.get(key);return prototype != null ? prototype.clone() : null; }
-
异常与资源管理 :妥善处理克隆过程中的异常和资源
// 推荐:完善的异常处理 @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 性能优化
-
克隆策略选择 :根据对象复杂度选择合适的克隆策略
- 简单对象:拷贝构造函数
- 复杂对象:序列化克隆
- 不可变部分:引用复制
-
延迟克隆 :对于大对象,考虑写时复制(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 架构设计
-
与工厂模式结合 :原型注册表可与工厂模式结合,提供统一的对象创建接口
-
线程安全考虑 :在多线程环境下,确保原型注册表和克隆操作的线程安全
// 推荐:线程安全的注册表 public class ThreadSafeRegistry {private static final ConcurrentHashMap<String, Prototype> prototypes = new ConcurrentHashMap<>(); }
-
版本控制 :为原型对象添加版本信息,支持向后兼容的序列化克隆
10. 参考资料与延伸阅读
- 《设计模式:可复用面向对象软件的基础》GoF
- Effective Java(中文版)
- https://refactoringguru.cn/design-patterns/prototype
- https://www.baeldung.com/java-prototype-pattern
本文为设计模式系列第8篇,后续每篇将聚焦一个设计模式或设计原则,深入讲解实现与应用,敬请关注。