原型模式(Prototype Pattern)详解

打印 上一主题 下一主题

主题 1749|帖子 1749|积分 5247

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
1. 什么是原型模式?

原型模式是一种创建型设计模式,它允许我们通过复制(克隆)现有对象来创建新对象,而不是通过利用构造函数创建。原型模式的核心头脑是基于现有对象创建新的对象,而不是从零开始创建
这种模式特殊实用于创建对象成本较高的场景,或者必要创建大量相似对象的情况。通过克隆现有对象,可以避免重新执行初始化过程,从而进步性能。
2. 为什么必要原型模式?

在以下情况下,原型模式特殊有效:

  • 当创建对象的过程很昂贵或复杂时(如必要进行数据库操作或文件I/O)
  • 当必要创建的对象与现有对象差异不大时
  • 当必要避免利用构造函数创建对象的限定时
  • 当必要生存对象状态并在必要时恢复时
  • 当体系必要独立于对象的创建方式时
3. 原型模式的结构

原型模式通常包含以下角色:

  • 原型接口(Prototype):声明克隆自身的方法
  • 具体原型(Concrete Prototype):实现克隆方法的类
  • 客户端(Client):利用原型实例创建新对象的类
4. 原型模式的根本实现

4.1 基础示例:简朴的原型模式

起首,我们界说一个原型接口:
  1. // 原型接口
  2. public interface Prototype {
  3.     Prototype clone();
  4. }
复制代码
然后,实现具体原型类:
  1. // 具体原型类
  2. public class ConcretePrototype implements Prototype {
  3.     private String field;
  4.    
  5.     public ConcretePrototype(String field) {
  6.         this.field = field;
  7.     }
  8.    
  9.     // 用于测试的getter和setter
  10.     public String getField() {
  11.         return field;
  12.     }
  13.    
  14.     public void setField(String field) {
  15.         this.field = field;
  16.     }
  17.    
  18.     @Override
  19.     public Prototype clone() {
  20.         return new ConcretePrototype(this.field);
  21.     }
  22.    
  23.     @Override
  24.     public String toString() {
  25.         return "ConcretePrototype [field=" + field + "]";
  26.     }
  27. }
复制代码
末了,客户端代码:
  1. // 客户端代码
  2. public class Client {
  3.     public static void main(String[] args) {
  4.         // 创建原型对象
  5.         ConcretePrototype prototype = new ConcretePrototype("原始值");
  6.         System.out.println("原型对象: " + prototype);
  7.         
  8.         // 克隆原型对象
  9.         ConcretePrototype clone = (ConcretePrototype) prototype.clone();
  10.         System.out.println("克隆对象: " + clone);
  11.         
  12.         // 修改克隆对象的属性
  13.         clone.setField("修改后的值");
  14.         System.out.println("修改后的克隆对象: " + clone);
  15.         System.out.println("原型对象: " + prototype); // 原型对象不受影响
  16.     }
  17. }
复制代码
输出效果:
  1. 原型对象: ConcretePrototype [field=原始值]
  2. 克隆对象: ConcretePrototype [field=原始值]
  3. 修改后的克隆对象: ConcretePrototype [field=修改后的值]
  4. 原型对象: ConcretePrototype [field=原始值]
复制代码
4.2 利用Java的Cloneable接口

Java提供了Cloneable接口和Object.clone()方法来支持原型模式。下面是利用Java内置机制的例子:
  1. // 使用Java的Cloneable接口实现原型模式
  2. public class Person implements Cloneable {
  3.     private String name;
  4.     private int age;
  5.     private String address;
  6.    
  7.     public Person(String name, int age, String address) {
  8.         this.name = name;
  9.         this.age = age;
  10.         this.address = address;
  11.     }
  12.    
  13.     // Getters and Setters
  14.     public String getName() { return name; }
  15.     public void setName(String name) { this.name = name; }
  16.    
  17.     public int getAge() { return age; }
  18.     public void setAge(int age) { this.age = age; }
  19.    
  20.     public String getAddress() { return address; }
  21.     public void setAddress(String address) { this.address = address; }
  22.    
  23.     @Override
  24.     public Person clone() {
  25.         try {
  26.             // 调用Object的clone方法
  27.             return (Person) super.clone();
  28.         } catch (CloneNotSupportedException e) {
  29.             // 实现了Cloneable接口的类不应该抛出这个异常
  30.             throw new AssertionError("这不应该发生", e);
  31.         }
  32.     }
  33.    
  34.     @Override
  35.     public String toString() {
  36.         return "Person [name=" + name + ", age=" + age + ", address=" + address + "]";
  37.     }
  38. }
复制代码
利用示例:
  1. public class CloneableDemo {
  2.     public static void main(String[] args) {
  3.         // 创建原型对象
  4.         Person original = new Person("张三", 30, "北京市海淀区");
  5.         System.out.println("原始对象: " + original);
  6.         
  7.         // 克隆对象
  8.         Person clone = original.clone();
  9.         System.out.println("克隆对象: " + clone);
  10.         
  11.         // 修改克隆对象
  12.         clone.setName("李四");
  13.         clone.setAge(25);
  14.         clone.setAddress("上海市浦东新区");
  15.         
  16.         // 查看修改后的结果
  17.         System.out.println("修改后的克隆对象: " + clone);
  18.         System.out.println("原始对象: " + original); // 原始对象不变
  19.     }
  20. }
复制代码
输出效果:
  1. 原始对象: Person [name=张三, age=30, address=北京市海淀区]
  2. 克隆对象: Person [name=张三, age=30, address=北京市海淀区]
  3. 修改后的克隆对象: Person [name=李四, age=25, address=上海市浦东新区]
  4. 原始对象: Person [name=张三, age=30, address=北京市海淀区]
复制代码
5. 深拷贝与浅拷贝

在原型模式中,有两种范例的复制:
5.1 浅拷贝(Shallow Copy)

浅拷贝只复制对象的根本属性,对于引用范例,只复制引用而不复制引用指向的对象。Java的Object.clone()方法默认执行浅拷贝。
  1. // 包含引用类型的类
  2. public class Employee implements Cloneable {
  3.     private String name;
  4.     private Department department; // 引用类型
  5.    
  6.     public Employee(String name, Department department) {
  7.         this.name = name;
  8.         this.department = department;
  9.     }
  10.    
  11.     // Getters and Setters
  12.     public String getName() { return name; }
  13.     public void setName(String name) { this.name = name; }
  14.    
  15.     public Department getDepartment() { return department; }
  16.     public void setDepartment(Department department) { this.department = department; }
  17.    
  18.     // 浅拷贝
  19.     @Override
  20.     public Employee clone() {
  21.         try {
  22.             return (Employee) super.clone();
  23.         } catch (CloneNotSupportedException e) {
  24.             throw new AssertionError(e);
  25.         }
  26.     }
  27.    
  28.     @Override
  29.     public String toString() {
  30.         return "Employee [name=" + name + ", department=" + department + "]";
  31.     }
  32. }
  33. // 引用类型
  34. public class Department {
  35.     private String name;
  36.    
  37.     public Department(String name) {
  38.         this.name = name;
  39.     }
  40.    
  41.     public String getName() { return name; }
  42.     public void setName(String name) { this.name = name; }
  43.    
  44.     @Override
  45.     public String toString() {
  46.         return "Department [name=" + name + "]";
  47.     }
  48. }
复制代码
浅拷贝示例:
  1. public class ShallowCopyDemo {
  2.     public static void main(String[] args) {
  3.         // 创建部门
  4.         Department hr = new Department("人力资源部");
  5.         
  6.         // 创建员工
  7.         Employee original = new Employee("张三", hr);
  8.         System.out.println("原始员工: " + original);
  9.         
  10.         // 浅拷贝
  11.         Employee clone = original.clone();
  12.         System.out.println("克隆员工: " + clone);
  13.         
  14.         // 修改克隆对象的引用类型属性
  15.         clone.getDepartment().setName("财务部");
  16.         
  17.         // 查看修改后的结果
  18.         System.out.println("修改后的克隆员工: " + clone);
  19.         System.out.println("原始员工: " + original); // 原始对象的引用类型也被修改了
  20.     }
  21. }
复制代码
输出效果:
  1. 原始员工: Employee [name=张三, department=Department [name=人力资源部]]
  2. 克隆员工: Employee [name=张三, department=Department [name=人力资源部]]
  3. 修改后的克隆员工: Employee [name=张三, department=Department [name=财务部]]
  4. 原始员工: Employee [name=张三, department=Department [name=财务部]]
复制代码
注意:修改克隆对象的引用范例属性会影响原始对象。
5.2 深拷贝(Deep Copy)

深拷贝不仅复制对象本身,还复制对象包含的全部引用范例的属性。有两种常见的实现方式:
5.2.1 通过递归复制实现深拷贝

  1. // 实现Cloneable接口的引用类型
  2. public class Department implements Cloneable {
  3.     private String name;
  4.    
  5.     public Department(String name) {
  6.         this.name = name;
  7.     }
  8.    
  9.     public String getName() { return name; }
  10.     public void setName(String name) { this.name = name; }
  11.    
  12.     @Override
  13.     protected Department clone() {
  14.         try {
  15.             return (Department) super.clone();
  16.         } catch (CloneNotSupportedException e) {
  17.             throw new AssertionError(e);
  18.         }
  19.     }
  20.    
  21.     @Override
  22.     public String toString() {
  23.         return "Department [name=" + name + "]";
  24.     }
  25. }
  26. // 深拷贝实现
  27. public class Employee implements Cloneable {
  28.     private String name;
  29.     private Department department;
  30.    
  31.     public Employee(String name, Department department) {
  32.         this.name = name;
  33.         this.department = department;
  34.     }
  35.    
  36.     // Getters and Setters
  37.     public String getName() { return name; }
  38.     public void setName(String name) { this.name = name; }
  39.    
  40.     public Department getDepartment() { return department; }
  41.     public void setDepartment(Department department) { this.department = department; }
  42.    
  43.     // 深拷贝
  44.     @Override
  45.     public Employee clone() {
  46.         try {
  47.             Employee cloned = (Employee) super.clone();
  48.             // 对引用类型也进行克隆
  49.             cloned.department = this.department.clone();
  50.             return cloned;
  51.         } catch (CloneNotSupportedException e) {
  52.             throw new AssertionError(e);
  53.         }
  54.     }
  55.    
  56.     @Override
  57.     public String toString() {
  58.         return "Employee [name=" + name + ", department=" + department + "]";
  59.     }
  60. }
复制代码
5.2.2 通过序列化实现深拷贝

利用序列化和反序列化实现深拷贝是一种更通用的方法,特殊实用于对象层次较深的情况:
  1. import java.io.*;
  2. // 必须实现Serializable接口
  3. public class Department implements Serializable {
  4.     private static final long serialVersionUID = 1L;
  5.     private String name;
  6.    
  7.     public Department(String name) {
  8.         this.name = name;
  9.     }
  10.    
  11.     public String getName() { return name; }
  12.     public void setName(String name) { this.name = name; }
  13.    
  14.     @Override
  15.     public String toString() {
  16.         return "Department [name=" + name + "]";
  17.     }
  18. }
  19. // 实现Serializable接口
  20. public class Employee implements Serializable {
  21.     private static final long serialVersionUID = 1L;
  22.     private String name;
  23.     private Department department;
  24.    
  25.     public Employee(String name, Department department) {
  26.         this.name = name;
  27.         this.department = department;
  28.     }
  29.    
  30.     // Getters and Setters
  31.     public String getName() { return name; }
  32.     public void setName(String name) { this.name = name; }
  33.    
  34.     public Department getDepartment() { return department; }
  35.     public void setDepartment(Department department) { this.department = department; }
  36.    
  37.     // 通过序列化实现深拷贝
  38.     public Employee deepCopy() {
  39.         try {
  40.             // 写入字节流
  41.             ByteArrayOutputStream bos = new ByteArrayOutputStream();
  42.             ObjectOutputStream oos = new ObjectOutputStream(bos);
  43.             oos.writeObject(this);
  44.             
  45.             // 从字节流读取
  46.             ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
  47.             ObjectInputStream ois = new ObjectInputStream(bis);
  48.             return (Employee) ois.readObject();
  49.         } catch (Exception e) {
  50.             throw new RuntimeException(e);
  51.         }
  52.     }
  53.    
  54.     @Override
  55.     public String toString() {
  56.         return "Employee [name=" + name + ", department=" + department + "]";
  57.     }
  58. }
复制代码
利用序列化深拷贝的示例:
  1. public class DeepCopySerializationDemo {
  2.     public static void main(String[] args) {
  3.         // 创建部门
  4.         Department hr = new Department("人力资源部");
  5.         
  6.         // 创建员工
  7.         Employee original = new Employee("张三", hr);
  8.         System.out.println("原始员工: " + original);
  9.         
  10.         // 深拷贝
  11.         Employee clone = original.deepCopy();
  12.         System.out.println("克隆员工: " + clone);
  13.         
  14.         // 修改克隆对象的引用类型属性
  15.         clone.getDepartment().setName("财务部");
  16.         
  17.         // 查看修改后的结果
  18.         System.out.println("修改后的克隆员工: " + clone);
  19.         System.out.println("原始员工: " + original); // 原始对象不受影响
  20.     }
  21. }
复制代码
输出效果:
  1. 原始员工: Employee [name=张三, department=Department [name=人力资源部]]
  2. 克隆员工: Employee [name=张三, department=Department [name=人力资源部]]
  3. 修改后的克隆员工: Employee [name=张三, department=Department [name=财务部]]
  4. 原始员工: Employee [name=张三, department=Department [name=人力资源部]]
复制代码
6. 原型模式的实际应用场景

6.1 数据对象的复制

当必要创建大量相似但略有差异的数据对象时,原型模式非常有效:
  1. public class DataObject implements Cloneable {
  2.     private String id;
  3.     private String name;
  4.     private String description;
  5.     private Date creationDate;
  6.    
  7.     // 构造函数和其他初始化代码...
  8.    
  9.     @Override
  10.     protected DataObject clone() {
  11.         try {
  12.             DataObject clone = (DataObject) super.clone();
  13.             // 对日期进行深拷贝
  14.             clone.creationDate = (Date) this.creationDate.clone();
  15.             return clone;
  16.         } catch (CloneNotSupportedException e) {
  17.             throw new AssertionError(e);
  18.         }
  19.     }
  20.    
  21.     // 创建一个带有不同ID的新实例
  22.     public DataObject cloneWithNewId(String newId) {
  23.         DataObject clone = this.clone();
  24.         clone.id = newId;
  25.         return clone;
  26.     }
  27. }
复制代码
6.2 对象的缓存

原型模式可以用于实现对象缓存,避免重复创建开销大的对象:
  1. 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;    }}// 原型接口
  2. public interface Prototype {
  3.     Prototype clone();
  4. }
  5. // 具体原型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();        }    }        @Override    public Document clone() {        return new Document(this.content, this.format);    }        // 其他方法...}
复制代码
利用示例:
  1. public class PrototypeManagerDemo {
  2.     public static void main(String[] args) {
  3.         // 初始化原型
  4.         Document wordDoc = new Document("示例内容", "Word");
  5.         Document pdfDoc = new Document("示例内容", "PDF");
  6.         
  7.         // 注册原型
  8.         PrototypeManager.register("word", wordDoc);
  9.         PrototypeManager.register("pdf", pdfDoc);
  10.         
  11.         // 使用原型创建对象
  12.         long start = System.currentTimeMillis();
  13.         Document doc1 = (Document) PrototypeManager.getPrototype("word");
  14.         Document doc2 = (Document) PrototypeManager.getPrototype("pdf");
  15.         long end = System.currentTimeMillis();
  16.         
  17.         System.out.println("克隆两个文档耗时: " + (end - start) + "毫秒"); // 几乎不耗时
  18.         
  19.         // 对比直接创建
  20.         start = System.currentTimeMillis();
  21.         Document doc3 = new Document("示例内容", "Word");
  22.         Document doc4 = new Document("示例内容", "PDF");
  23.         end = System.currentTimeMillis();
  24.         
  25.         System.out.println("直接创建两个文档耗时: " + (end - start) + "毫秒"); // 约2000毫秒
  26.     }
  27. }
复制代码
6.3 Java中的实际应用

Java尺度库中也有一些类利用了原型模式:

  • Java集合框架中的clone()方法
  • Object类提供的clone()方法
7. 原型模式与其他设计模式的区别

7.1 原型模式 vs 工厂模式

原型模式工厂模式通过复制现有对象创建新对象通过工厂类创建对象不必要关心类的具体实现细节必要知道具体的产品类适合创建成本高的对象适合创建多种不同范例的对象 7.2 原型模式 vs 制作者模式

原型模式制作者模式通过克隆创建对象分步调创建复杂对象基于现有对象创建从零开始构建对象适合创建相似对象适合构建复杂对象 8. 原型模式的优缺点

8.1 优点


  • 减少对象创建的成本,特殊是创建过程复杂或耗时的情况
  • 潜伏对象创建的细节
  • 允许在运行时添加和删除产品
  • 提供了一种快速创建复杂对象的方法
  • 可以避免构造函数的限定,如创建不可变对象的多个变体
8.2 缺点


  • 对象包含循环引用时的克隆大概很复杂
  • 深拷贝实现复杂,特殊是对象结构较深时
  • 克隆包含引用范例的对象时必要格外小心(浅拷贝问题)
  • 对于一些有状态的对象,大概必要重置状态
9. 何时利用原型模式?

以下情况适合利用原型模式:

  • 当创建对象的代价较大,且创建的对象之间差异较小时
  • 当必要避免构建与产品类层次平行的工厂类层次时
  • 当对象的类在运行时才确定时
  • 当体系必要独立于产品如何创建、组合和表示时
  • 当必要生存对象的状态,并在未来必要恢复到这个状态时
10. 常见问题及解决方案

10.1 问题:如何处理深拷贝中的循环引用?

解决方案:利用哈希表纪录已经克隆过的对象,避免重复克隆。
  1. import java.util.HashMap;
  2. import java.util.Map;
  3. public class DeepCopyUtil {
  4.     private static Map<Object, Object> clonedObjects = new HashMap<>();
  5.    
  6.     public static <T> T deepCopy(T object) {
  7.         // 如果对象已经被克隆,直接返回克隆的对象
  8.         if (clonedObjects.containsKey(object)) {
  9.             return (T) clonedObjects.get(object);
  10.         }
  11.         
  12.         // 这里实现深拷贝逻辑
  13.         // ...
  14.         
  15.         // 将克隆对象添加到哈希表中
  16.         clonedObjects.put(object, clonedObject);
  17.         
  18.         return clonedObject;
  19.     }
  20. }
复制代码
10.2 问题:如何克隆不可变对象?

解决方案:对于不可变对象,可以直接返回对象本身,不必要克隆。
  1. public class ImmutableObject implements Cloneable {
  2.     private final String data;
  3.    
  4.     public ImmutableObject(String data) {
  5.         this.data = data;
  6.     }
  7.    
  8.     public String getData() {
  9.         return data;
  10.     }
  11.    
  12.     @Override
  13.     public ImmutableObject clone() {
  14.         // 对于不可变对象,可以直接返回对象本身
  15.         return this;
  16.     }
  17. }
复制代码
10.3 问题:如何包管克隆对象和原型对象的一致性?

解决方案:在克隆过程中添加验证逻辑,确保克隆对象符合要求。
  1. @Override
  2. public Object clone() {
  3.     try {
  4.         Object clone = super.clone();
  5.         // 添加验证逻辑
  6.         validate(clone);
  7.         return clone;
  8.     } catch (CloneNotSupportedException e) {
  9.         throw new AssertionError(e);
  10.     }
  11. }
  12. private void validate(Object clone) {
  13.     // 验证逻辑
  14.     // ...
  15.     if (!isValid(clone)) {
  16.         throw new IllegalStateException("克隆对象验证失败");
  17.     }
  18. }
复制代码
11. 总结

原型模式是一种强大的创建型设计模式,它允许通过克隆现有对象来创建新对象,避免了昂贵的对象创建过程。在Java中,我们可以通过实现Cloneable接口和重写clone()方法来实现原型模式。
原型模式的关键点在于理解浅拷贝和深拷贝的区别,以及如何根据具体需求选择合适的克隆方式。对于包含引用范例的复杂对象,通常必要实现深拷贝以避免潜在的问题。
通过本文的多个示例,盼望初学者能够对原型模式有一个全面深入的理解,并能在一样寻常编程中机动运用这一模式。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

卖不甜枣

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表