原型模式(Prototype Pattern)详解
文章目录
- 1. 什么是原型模式?
- 2. 为什么需要原型模式?
- 3. 原型模式的结构
- 4. 原型模式的基本实现
- 4.1 基础示例:简单的原型模式
- 4.2 使用Java的Cloneable接口
- 5. 深拷贝与浅拷贝
- 5.1 浅拷贝(Shallow Copy)
- 5.2 深拷贝(Deep Copy)
- 5.2.1 通过递归复制实现深拷贝
- 5.2.2 通过序列化实现深拷贝
- 6. 原型模式的实际应用场景
- 6.1 数据对象的复制
- 6.2 对象的缓存
- 6.3 Java中的实际应用
- 7. 原型模式与其他设计模式的区别
- 7.1 原型模式 vs 工厂模式
- 7.2 原型模式 vs 建造者模式
- 8. 原型模式的优缺点
- 8.1 优点
- 8.2 缺点
- 9. 何时使用原型模式?
- 10. 常见问题及解决方案
- 10.1 问题:如何处理深拷贝中的循环引用?
- 10.2 问题:如何克隆不可变对象?
- 10.3 问题:如何保证克隆对象和原型对象的一致性?
- 11. 总结
1. 什么是原型模式?
原型模式是一种创建型设计模式,它允许我们通过复制(克隆)现有对象来创建新对象,而不是通过使用构造函数创建。原型模式的核心思想是基于现有对象创建新的对象,而不是从零开始创建。
这种模式特别适用于创建对象成本较高的场景,或者需要创建大量相似对象的情况。通过克隆现有对象,可以避免重新执行初始化过程,从而提高性能。
2. 为什么需要原型模式?
在以下情况下,原型模式特别有用:
- 当创建对象的过程很昂贵或复杂时(如需要进行数据库操作或文件I/O)
- 当需要创建的对象与现有对象差别不大时
- 当需要避免使用构造函数创建对象的限制时
- 当需要保存对象状态并在需要时恢复时
- 当系统需要独立于对象的创建方式时
3. 原型模式的结构
原型模式通常包含以下角色:
- 原型接口(Prototype):声明克隆自身的方法
- 具体原型(Concrete Prototype):实现克隆方法的类
- 客户端(Client):使用原型实例创建新对象的类
4. 原型模式的基本实现
4.1 基础示例:简单的原型模式
首先,我们定义一个原型接口:
// 原型接口
public interface Prototype {Prototype clone();
}
然后,实现具体原型类:
// 具体原型类
public class ConcretePrototype implements Prototype {private String field;public ConcretePrototype(String field) {this.field = field;}// 用于测试的getter和setterpublic String getField() {return field;}public void setField(String field) {this.field = field;}@Overridepublic Prototype clone() {return new ConcretePrototype(this.field);}@Overridepublic String toString() {return "ConcretePrototype [field=" + field + "]";}
}
最后,客户端代码:
// 客户端代码
public class Client {public static void main(String[] args) {// 创建原型对象ConcretePrototype prototype = new ConcretePrototype("原始值");System.out.println("原型对象: " + prototype);// 克隆原型对象ConcretePrototype clone = (ConcretePrototype) prototype.clone();System.out.println("克隆对象: " + clone);// 修改克隆对象的属性clone.setField("修改后的值");System.out.println("修改后的克隆对象: " + clone);System.out.println("原型对象: " + prototype); // 原型对象不受影响}
}
输出结果:
原型对象: ConcretePrototype [field=原始值]
克隆对象: ConcretePrototype [field=原始值]
修改后的克隆对象: ConcretePrototype [field=修改后的值]
原型对象: ConcretePrototype [field=原始值]
4.2 使用Java的Cloneable接口
Java提供了Cloneable
接口和Object.clone()
方法来支持原型模式。下面是使用Java内置机制的例子:
// 使用Java的Cloneable接口实现原型模式
public class Person implements Cloneable {private String name;private int age;private String address;public Person(String name, int age, String address) {this.name = name;this.age = age;this.address = address;}// Getters and Setterspublic String getName() { return name; }public void setName(String name) { this.name = name; }public int getAge() { return age; }public void setAge(int age) { this.age = age; }public String getAddress() { return address; }public void setAddress(String address) { this.address = address; }@Overridepublic Person clone() {try {// 调用Object的clone方法return (Person) super.clone();} catch (CloneNotSupportedException e) {// 实现了Cloneable接口的类不应该抛出这个异常throw new AssertionError("这不应该发生", e);}}@Overridepublic String toString() {return "Person [name=" + name + ", age=" + age + ", address=" + address + "]";}
}
使用示例:
public class CloneableDemo {public static void main(String[] args) {// 创建原型对象Person original = new Person("张三", 30, "北京市海淀区");System.out.println("原始对象: " + original);// 克隆对象Person clone = original.clone();System.out.println("克隆对象: " + clone);// 修改克隆对象clone.setName("李四");clone.setAge(25);clone.setAddress("上海市浦东新区");// 查看修改后的结果System.out.println("修改后的克隆对象: " + clone);System.out.println("原始对象: " + original); // 原始对象不变}
}
输出结果:
原始对象: Person [name=张三, age=30, address=北京市海淀区]
克隆对象: Person [name=张三, age=30, address=北京市海淀区]
修改后的克隆对象: Person [name=李四, age=25, address=上海市浦东新区]
原始对象: Person [name=张三, age=30, address=北京市海淀区]
5. 深拷贝与浅拷贝
在原型模式中,有两种类型的复制:
5.1 浅拷贝(Shallow Copy)
浅拷贝只复制对象的基本属性,对于引用类型,只复制引用而不复制引用指向的对象。Java的Object.clone()
方法默认执行浅拷贝。
// 包含引用类型的类
public class Employee implements Cloneable {private String name;private Department department; // 引用类型public Employee(String name, Department department) {this.name = name;this.department = department;}// Getters and Setterspublic String getName() { return name; }public void setName(String name) { this.name = name; }public Department getDepartment() { return department; }public void setDepartment(Department department) { this.department = department; }// 浅拷贝@Overridepublic Employee clone() {try {return (Employee) super.clone();} catch (CloneNotSupportedException e) {throw new AssertionError(e);}}@Overridepublic String toString() {return "Employee [name=" + name + ", department=" + department + "]";}
}// 引用类型
public class Department {private String name;public Department(String name) {this.name = name;}public String getName() { return name; }public void setName(String name) { this.name = name; }@Overridepublic String toString() {return "Department [name=" + name + "]";}
}
浅拷贝示例:
public class ShallowCopyDemo {public static void main(String[] args) {// 创建部门Department hr = new Department("人力资源部");// 创建员工Employee original = new Employee("张三", hr);System.out.println("原始员工: " + original);// 浅拷贝Employee clone = original.clone();System.out.println("克隆员工: " + clone);// 修改克隆对象的引用类型属性clone.getDepartment().setName("财务部");// 查看修改后的结果System.out.println("修改后的克隆员工: " + clone);System.out.println("原始员工: " + original); // 原始对象的引用类型也被修改了}
}
输出结果:
原始员工: Employee [name=张三, department=Department [name=人力资源部]]
克隆员工: Employee [name=张三, department=Department [name=人力资源部]]
修改后的克隆员工: Employee [name=张三, department=Department [name=财务部]]
原始员工: Employee [name=张三, department=Department [name=财务部]]
注意:修改克隆对象的引用类型属性会影响原始对象。
5.2 深拷贝(Deep Copy)
深拷贝不仅复制对象本身,还复制对象包含的所有引用类型的属性。有两种常见的实现方式:
5.2.1 通过递归复制实现深拷贝
// 实现Cloneable接口的引用类型
public class Department implements Cloneable {private String name;public Department(String name) {this.name = name;}public String getName() { return name; }public void setName(String name) { this.name = name; }@Overrideprotected Department clone() {try {return (Department) super.clone();} catch (CloneNotSupportedException e) {throw new AssertionError(e);}}@Overridepublic String toString() {return "Department [name=" + name + "]";}
}// 深拷贝实现
public class Employee implements Cloneable {private String name;private Department department;public Employee(String name, Department department) {this.name = name;this.department = department;}// Getters and Setterspublic String getName() { return name; }public void setName(String name) { this.name = name; }public Department getDepartment() { return department; }public void setDepartment(Department department) { this.department = department; }// 深拷贝@Overridepublic Employee clone() {try {Employee cloned = (Employee) super.clone();// 对引用类型也进行克隆cloned.department = this.department.clone();return cloned;} catch (CloneNotSupportedException e) {throw new AssertionError(e);}}@Overridepublic String toString() {return "Employee [name=" + name + ", department=" + department + "]";}
}
5.2.2 通过序列化实现深拷贝
使用序列化和反序列化实现深拷贝是一种更通用的方法,特别适用于对象层次较深的情况:
import java.io.*;// 必须实现Serializable接口
public class Department implements Serializable {private static final long serialVersionUID = 1L;private String name;public Department(String name) {this.name = name;}public String getName() { return name; }public void setName(String name) { this.name = name; }@Overridepublic String toString() {return "Department [name=" + name + "]";}
}// 实现Serializable接口
public class Employee implements Serializable {private static final long serialVersionUID = 1L;private String name;private Department department;public Employee(String name, Department department) {this.name = name;this.department = department;}// Getters and Setterspublic String getName() { return name; }public void setName(String name) { this.name = name; }public Department getDepartment() { return department; }public void setDepartment(Department department) { this.department = department; }// 通过序列化实现深拷贝public Employee deepCopy() {try {// 写入字节流ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(this);// 从字节流读取ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bis);return (Employee) ois.readObject();} catch (Exception e) {throw new RuntimeException(e);}}@Overridepublic String toString() {return "Employee [name=" + name + ", department=" + department + "]";}
}
使用序列化深拷贝的示例:
public class DeepCopySerializationDemo {public static void main(String[] args) {// 创建部门Department hr = new Department("人力资源部");// 创建员工Employee original = new Employee("张三", hr);System.out.println("原始员工: " + original);// 深拷贝Employee clone = original.deepCopy();System.out.println("克隆员工: " + clone);// 修改克隆对象的引用类型属性clone.getDepartment().setName("财务部");// 查看修改后的结果System.out.println("修改后的克隆员工: " + clone);System.out.println("原始员工: " + original); // 原始对象不受影响}
}
输出结果:
原始员工: Employee [name=张三, department=Department [name=人力资源部]]
克隆员工: Employee [name=张三, department=Department [name=人力资源部]]
修改后的克隆员工: Employee [name=张三, department=Department [name=财务部]]
原始员工: Employee [name=张三, department=Department [name=人力资源部]]
6. 原型模式的实际应用场景
6.1 数据对象的复制
当需要创建大量相似但略有差异的数据对象时,原型模式非常有用:
public class DataObject implements Cloneable {private String id;private String name;private String description;private Date creationDate;// 构造函数和其他初始化代码...@Overrideprotected DataObject clone() {try {DataObject clone = (DataObject) super.clone();// 对日期进行深拷贝clone.creationDate = (Date) this.creationDate.clone();return clone;} catch (CloneNotSupportedException e) {throw new AssertionError(e);}}// 创建一个带有不同ID的新实例public DataObject cloneWithNewId(String newId) {DataObject clone = this.clone();clone.id = newId;return clone;}
}
6.2 对象的缓存
原型模式可以用于实现对象缓存,避免重复创建开销大的对象:
import java.util.HashMap;
import java.util.Map;// 原型管理器
public class PrototypeManager {private static Map<String, Prototype> prototypes = new HashMap<>();// 注册原型public static void register(String key, Prototype prototype) {prototypes.put(key, prototype);}// 获取原型的克隆public static Prototype getPrototype(String key) {Prototype prototype = prototypes.get(key);if (prototype != null) {return prototype.clone();}return null;}
}// 原型接口
public interface Prototype {Prototype clone();
}// 具体原型
public class Document implements Prototype {private String content;private String format;public Document(String content, String format) {this.content = content;this.format = format;// 假设初始化过程非常耗时try {Thread.sleep(1000); // 模拟耗时操作} catch (InterruptedException e) {e.printStackTrace();}}@Overridepublic Document clone() {return new Document(this.content, this.format);}// 其他方法...
}
使用示例:
public class PrototypeManagerDemo {public static void main(String[] args) {// 初始化原型Document wordDoc = new Document("示例内容", "Word");Document pdfDoc = new Document("示例内容", "PDF");// 注册原型PrototypeManager.register("word", wordDoc);PrototypeManager.register("pdf", pdfDoc);// 使用原型创建对象long start = System.currentTimeMillis();Document doc1 = (Document) PrototypeManager.getPrototype("word");Document doc2 = (Document) PrototypeManager.getPrototype("pdf");long end = System.currentTimeMillis();System.out.println("克隆两个文档耗时: " + (end - start) + "毫秒"); // 几乎不耗时// 对比直接创建start = System.currentTimeMillis();Document doc3 = new Document("示例内容", "Word");Document doc4 = new Document("示例内容", "PDF");end = System.currentTimeMillis();System.out.println("直接创建两个文档耗时: " + (end - start) + "毫秒"); // 约2000毫秒}
}
6.3 Java中的实际应用
Java标准库中也有一些类使用了原型模式:
- Java集合框架中的
clone()
方法 - Object类提供的
clone()
方法
7. 原型模式与其他设计模式的区别
7.1 原型模式 vs 工厂模式
原型模式 | 工厂模式 |
---|---|
通过复制现有对象创建新对象 | 通过工厂类创建对象 |
不需要关心类的具体实现细节 | 需要知道具体的产品类 |
适合创建成本高的对象 | 适合创建多种不同类型的对象 |
7.2 原型模式 vs 建造者模式
原型模式 | 建造者模式 |
---|---|
通过克隆创建对象 | 分步骤创建复杂对象 |
基于现有对象创建 | 从零开始构建对象 |
适合创建相似对象 | 适合构建复杂对象 |
8. 原型模式的优缺点
8.1 优点
- 减少对象创建的成本,特别是创建过程复杂或耗时的情况
- 隐藏对象创建的细节
- 允许在运行时添加和删除产品
- 提供了一种快速创建复杂对象的方法
- 可以避免构造函数的限制,如创建不可变对象的多个变体
8.2 缺点
- 对象包含循环引用时的克隆可能很复杂
- 深拷贝实现复杂,特别是对象结构较深时
- 克隆包含引用类型的对象时需要格外小心(浅拷贝问题)
- 对于一些有状态的对象,可能需要重置状态
9. 何时使用原型模式?
以下情况适合使用原型模式:
- 当创建对象的代价较大,且创建的对象之间差异较小时
- 当需要避免构建与产品类层次平行的工厂类层次时
- 当对象的类在运行时才确定时
- 当系统需要独立于产品如何创建、组合和表示时
- 当需要保存对象的状态,并在将来需要恢复到这个状态时
10. 常见问题及解决方案
10.1 问题:如何处理深拷贝中的循环引用?
解决方案:使用哈希表记录已经克隆过的对象,避免重复克隆。
import java.util.HashMap;
import java.util.Map;public class DeepCopyUtil {private static Map<Object, Object> clonedObjects = new HashMap<>();public static <T> T deepCopy(T object) {// 如果对象已经被克隆,直接返回克隆的对象if (clonedObjects.containsKey(object)) {return (T) clonedObjects.get(object);}// 这里实现深拷贝逻辑// ...// 将克隆对象添加到哈希表中clonedObjects.put(object, clonedObject);return clonedObject;}
}
10.2 问题:如何克隆不可变对象?
解决方案:对于不可变对象,可以直接返回对象本身,不需要克隆。
public class ImmutableObject implements Cloneable {private final String data;public ImmutableObject(String data) {this.data = data;}public String getData() {return data;}@Overridepublic ImmutableObject clone() {// 对于不可变对象,可以直接返回对象本身return this;}
}
10.3 问题:如何保证克隆对象和原型对象的一致性?
解决方案:在克隆过程中添加验证逻辑,确保克隆对象符合要求。
@Override
public Object clone() {try {Object clone = super.clone();// 添加验证逻辑validate(clone);return clone;} catch (CloneNotSupportedException e) {throw new AssertionError(e);}
}private void validate(Object clone) {// 验证逻辑// ...if (!isValid(clone)) {throw new IllegalStateException("克隆对象验证失败");}
}
11. 总结
原型模式是一种强大的创建型设计模式,它允许通过克隆现有对象来创建新对象,避免了昂贵的对象创建过程。在Java中,我们可以通过实现Cloneable
接口和重写clone()
方法来实现原型模式。
原型模式的关键点在于理解浅拷贝和深拷贝的区别,以及如何根据具体需求选择合适的克隆方式。对于包含引用类型的复杂对象,通常需要实现深拷贝以避免潜在的问题。
通过本文的多个示例,希望初学者能够对原型模式有一个全面深入的理解,并能在日常编程中灵活运用这一模式。