系列导读:完成创建型模式的学习,我们来看最后一个创建型模式——原型模式。它通过复制已有对象来创建新对象,是一种独特的创建方式。
解决什么问题:通过复制现有对象来创建新对象,而不是重新实例化。适用于对象创建成本高、需要保持状态的场景。
在实际开发中,有时候创建一个对象的成本很高,比如需要从数据库查询大量数据、进行复杂计算、或者建立网络连接等。如果需要创建多个相似的对象,每次都重新执行这些操作就太浪费了。
原型模式提供了一个聪明的解决方案:先创建一个原型对象,然后通过复制这个原型来创建新对象。这样既保留了对象的状态,又避免了重复的创建成本。
本文在系列中的位置:
- 前置知识:建造者模式
- 系列角色:创建型模式收尾
- 难度等级:★★★☆☆(需要理解深拷贝和浅拷贝)
- 后续学习:结构型模式:适配器模式
目录
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 <|.. 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 :不可变对象使用原型模式
对于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篇,后续每篇将聚焦一个设计模式或设计原则,深入讲解实现与应用,敬请关注。