原型模式
什么是原型模式
原型模式(Prototype),用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
原型模式其实就是从一个对象再创建另外一个可定制的对象,而且不需知道任何创建的细节
示例
在 Java 中实现原型模式,我们通常会使用 Cloneable 接口和 clone() 方法来创建对象的副本。以下是一个使用原型模式编写的示例代码,以创建员工(Employee)对象的克隆为例:
1.首先定义一个实现了 Cloneable 接口的员工类 Employee,并重写 clone() 方法:
import java.util.Date;
public class Employee implements Cloneable {
private String name;
private int id;
private double salary;
private Date hireDate;
public Employee(String name, int id, double salary, Date hireDate) {
this.name = name;
this.id = id;
this.salary = salary;
this.hireDate = (Date) hireDate.clone(); // 防止浅拷贝时日期对象引用共享
}
public void setName(String name) {
this.name = name;
}
public void setId(int id) {
this.id = id;
}
public void setSalary(double salary) {
this.salary = salary;
}
public void setHireDate(Date hireDate) {
this.hireDate = (Date) hireDate.clone(); // 防止浅拷贝时日期对象引用共享
}
public String getName() {
return name;
}
public int getId() {
return id;
}
public double getSalary() {
return salary;
}
public Date getHireDate() {
return (Date) hireDate.clone(); // 防止浅拷贝时日期对象引用共享
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
这里,员工类实现了 Cloneable 接口,并重写了 clone() 方法。由于 Date 类型的属性可能引起浅拷贝问题,我们在构造器和 getter/setter 中使用 clone() 方法复制 hireDate,确保深拷贝。
2.在客户端代码中,创建一个员工对象,并使用 clone() 方法创建其副本:
import java.text.SimpleDateFormat;
import java.util.Date;
public class PrototypeDemo {
public static void main(String[] args) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
try {
// 创建原始员工对象
Employee original = new Employee("Alice", 1, 5000.0, sdf.parse("2023-06-9"));
// 使用 clone() 方法创建员工副本
Employee cloned = (Employee) original.clone();
// 改变原始员工的信息
original.setName("Bob");
original.setSalary(6000.0);
original.setHireDate(sdf.parse("2023-0¾-10"));
// 输出原始员工和克隆员工的信息
System.out.println("Original Employee:");
System.out.println("Name: " + original.getName());
System.out.println("ID: " + original.getId());
System.out.println("Salary: " + original.getSalary());
System.out.println("Hire Date: " + sdf.format(original.getHireDate()));
System.out.println("\nCloned Employee:");
System.out.println("Name: " + cloned.getName());
System.out.println("ID: " + cloned.getId());
System.out.println("Salary: " + cloned.getSalary());
System.out.println("Hire Date: " + sdf.format(cloned.getHireDate()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
什么是深拷贝
深拷贝是指在复制对象时,不仅复制对象本身,还递归地复制其所有包含的引用对象,直到所有的嵌套层次的对象都被复制为止。这意味着深拷贝创建的新对象与原对象具有完全相同的结构和数据,但它们在内存中占用独立的空间,互不影响。当对任何一个对象进行修改时,都不会影响到另一个对象,因为它们的内部状态(包括所引用对象的状态)都是独立存储的。
在 Java 中实现深拷贝,可以采用以下几种常见方法:
- 实现 Cloneable 接口并重写 clone() 方法
需要注意处理引用类型的属性,确保它们也被深拷贝。
- 使用序列化(Serializable)和反序列化
通过 ObjectOutputStream 写入字节流,再使用 ObjectInputStream 读取字节流,实现深拷贝
- 手动复制所有属性
对于简单对象,可以直接创建新对象并逐一复制每个属性值;对于复杂对象,需要递归地复制所有嵌套的对象。
这里我们以实现 Cloneable 接口并重写 clone() 方法的方式,编写一个包含员工(Employee)和地址(Address)类的深拷贝示例:
public class Address implements Cloneable {
private String street;
private String city;
public Address(String street, String city) {
this.street = street;
this.city = city;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Employee 类:
public class Employee implements Cloneable {
private String name;
private int age;
private Address address;
public Employee(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = (Address) address.clone(); // 防止浅拷贝
}
public 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 Address getAddress() {
return (Address) address.clone(); // 防止浅拷贝
}
public void setAddress(Address address) {
this.address = (Address) address.clone(); // 防止浅拷贝
}
@Override
protected Object clone() throws CloneNotSupportedException {
Employee clonedEmployee = (Employee) super.clone();
clonedEmployee.address = (Address) address.clone(); // 深拷贝地址对象
return clonedEmployee;
}
}
客户端代码
public class DeepCopyDemo {
public static void main(String[] args) {
Address originalAddress = new Address("123 Main St", "New York");
Employee originalEmployee = new Employee("John Doe", 30, originalAddress);
try {
Employee clonedEmployee = (Employee) originalEmployee.clone();
// 修改克隆对象的属性
clonedEmployee.setName("Jane Smith");
clonedEmployee.getAddress().setCity("Los Angeles");
System.out.println("Original Employee:");
System.out.println("Name: " + originalEmployee.getName());
System.out.println("Age: " + originalEmployee.getAge());
System.out.println("Address: " + originalEmployee.getAddress().getStreet() + ", " + originalEmployee.getAddress().getCity());
System.out.println("\nCloned Employee:");
System.out.println("Name: " + clonedEmployee.getName());
System.out.println("Age: " + clonedEmployee.getAge());
System.out.println("Address: " + clonedEmployee.getAddress().getStreet() + ", " + clonedEmployee.getAddress().getCity());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
运行 DeepCopyDemo 类的 main 方法,输出结果应为:
Original Employee:
Name: John Doe
Age: 30
Address: 123 Main St, New York
Cloned Employee:
Name: Jane Smith
Age: 30
Address: 123 Main St, Los Angeles
在这个示例中,我们通过实现 Cloneable 接口并重写 clone() 方法,实现了 Employee 和 Address 类的深拷贝。当修改克隆对象的属性时,原对象的属性保持不变,证明了深拷贝的成功。
什么是浅拷贝
浅拷贝是指在复制对象时,只复制对象本身,而不复制其包含的引用对象。也就是说,如果一个对象包含其他对象的引用,那么浅拷贝只会复制这些引用,而不是复制引用所指向的对象。结果是,新旧对象在内存中占据不同的位置,但它们内部的引用对象仍然指向同一块内存空间。因此,当其中一个对象修改其引用对象的状态时,会影响到另一个对象,因为它们共享了同一份数据。
假设我们有以下两个类:Person 类和 Address 类。Person 类包含一个 name 字符串属性和一个 address 引用属性,指向一个 Address 对象。
public class Address {
private String street;
private String city;
public Address(String street, String city) {
this.street = street;
this.city = city;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
public class Person {
private String name;
private Address address;
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
// 实现浅拷贝
public Person shallowCopy() {
return new Person(name, address);
}
}
客户端代码
public class ShallowCopyDemo {
public static void main(String[] args) {
Address originalAddress = new Address("123 Main St", "New York");
Person originalPerson = new Person("John Doe", originalAddress);
Person copiedPerson = originalPerson.shallowCopy();
// 修改复制对象的地址属性
copiedPerson.getAddress().setCity("Los Angeles");
System.out.println("Original Person:");
System.out.println("Name: " + originalPerson.getName());
System.out.println("Address: " + originalPerson.getAddress().getStreet() + ", " + originalPerson.getAddress().getCity());
System.out.println("\nCopied Person:");
System.out.println("Name: " + copiedPerson.getName());
System.out.println("Address: " + copiedPerson.getAddress().getStreet() + ", " + copiedPerson.getAddress().getCity());
}
}
运行 ShallowCopyDemo 类的 main 方法,输出结果应为:
Original Person:
Name: John Doe
Address: 123 Main St, Los Angeles
Copied Person:
Name: John Doe
Address: 123 Main St, Los Angeles
在这个示例中,我们通过 Person 类的 shallowCopy() 方法实现了浅拷贝。当修改复制对象的地址属性时,原对象的地址属性也被同步修改,表明它们共享了同一个 Address 对象。这就是浅拷贝的表现:虽然 originalPerson 和 copiedPerson 是两个不同的 Person 对象,但它们的 address 属性指向的是同一份 Address 数据。