原型模式(Prototype Patter)属于创建型模式,它提供了一种创建重复对象的最佳方式。其定义是:“用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。”
1. 模式介绍
原型模式(Prototype Patter)属于创建型模式,它提供了一种创建重复对象的最佳方式。其定义是:”用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。“
2. 应用场景
原型模式主要解决的问题是如何更好地创建一个重复对象。
值得注意的是,使用原型模式有一个前提,就是创建对象的成本较大,且新旧对象之间差异并不大,否则就是将简单问题复杂化了。例如对象需要从复杂 IO 中获取时,使用原型模式创建的性能就会比从 IO 中获取的性能要高。
3. 实现方式
在 Java 中,我们可以通过 Object#clone() 实现基于拷贝创建对象,下面是一个简单的示例。
// 用户类
public class User {
private String name;
private int age;
private Address address;
public User(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
}
// 地址类
public class Address {
private String addressName;
public Address(String addressName) {
this.addressName = addressName;
}
}
首先创建两个类,User 与 Address,然后在 main 方法中使用 Object#clone() 方法进行克隆操作。
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("杭州");
User user = new User("以东", 18, address);
System.out.println(user);
User cloneUser = (User) user.clone();
System.out.println(cloneUser);
}
运行结果如下:
io.walkers.planes.pandora.design.patterns.prototype.User@6e0be858
Exception in thread "main" java.lang.CloneNotSupportedException: io.walkers.planes.pandora.design.patterns.prototype.User
at java.lang.Object.clone(Native Method)
at io.walkers.planes.pandora.design.patterns.prototype.User.main(User.java:31)
咋回事?怎么就报错了呢?不是说在 Java 中通过 Object#clone() 就可以实现克隆吗?想要究其原因,还得翻开 Object#clone() 源码上的注释看看。
Throws:
CloneNotSupportedException – if the object's class does not support the Cloneable interface. Subclasses that override the clone method can also throw this exception to indicate that an instance cannot be cloned.
Object#clone() 方法会抛出 CloneNotSupportedException 异常,在注释中说明了抛出异常的情况:当类没有实现 Cloneable 接口时,调用 Object#clone() 方法会抛出 CloneNotSupportedException 异常。这是 JDK 对于 clone 方法做出的规定,所以当我们在使用 Object#clone() 前需要保证类已经实现了 Cloneable 接口。
在知道原因后,将 User 类实现 Cloneable 接口,再次执行 main 方法,结果如下:
io.walkers.planes.pandora.design.patterns.prototype.User@6e0be858
io.walkers.planes.pandora.design.patterns.prototype.User@61bbe9ba
输出结果中两个 User 对象地址并不相同,表明克隆成功。
但其实这里隐藏了一个细节,要知道在 User 类中包含了一个 Address 对象,目前我们已知在经过 clone 后 User 对象的地址是不同的,那么 Address 对象呢?为了验证这个问题,我们需要重写一下 User 类的 toString 方法。
@Override
public String toString() {
return "User{" + "name='" + name + '\'' + ", age=" + age + ", address=" + address + '}';
}
然后再次运行 main 方法,得到输出结果如下:
User{name='以东', age=18, address=io.walkers.planes.pandora.design.patterns.prototype.Address@6e0be858}
User{name='以东', age=18, address=io.walkers.planes.pandora.design.patterns.prototype.Address@6e0be858}
可以看到,Address 对象的地址值是相同的,也就是说在进行克隆时,默认情况下并不会对类中包含的引用类型对象进行克隆,而只是复制一份它的引用地址。
那么如果现在有一个需求是要得到一份完全是新的重复对象,该怎么处理呢?其实这涉及到了原型模式的两种实现方式——浅拷贝与深拷贝。
4. 浅拷贝与深拷贝
在进行数据拷贝时,只拷贝基本类型数据以及引用类型对象的地址,并不会递归地拷贝引用类型对象本身的形式,被称为浅拷贝。
相应的,在进行数据拷贝时,除了拷贝基本类型数据,同时会也会将引用类型对象本身(包括引用类型对象中包含的其他引用类型对象)进行拷贝的形式,即拷贝后的对象是完全新的对象,被称为深拷贝。
例如在上面介绍的原型模式 demo 示例中,就是浅拷贝的示例(Address 对象的地址相同)。如果我想将它修改成深拷贝形式,则需要重写 User 类的 clone 方法,如下所示:
public class User implements Cloneable {
// 省略其他方法与属性
@Override
protected Object clone() throws CloneNotSupportedException {
this.address = (Address) address.clone();
return super.clone();
}
}
需要注意 Address 类也得实现 Cloneable 接口,如果 Address 类中有其他引用类型的对象,也需要重写 Address 类的 clone 方法,以此递归下去…
在完成改造之后,输出结果如下:
User{name='以东', age=18, address=io.walkers.planes.pandora.design.patterns.prototype.Address@6e0be858}
User{name='以东', age=18, address=io.walkers.planes.pandora.design.patterns.prototype.Address@61bbe9ba}
两个 User 对象中的 Address 对象地址不同,表明这是一个深拷贝。
当然我们还有另一种深拷贝的方式:先将对象序列化,再反序列化为一个新的对象。但是这种方法相对而言更耗时。
关于何时使用浅拷贝,何时使用深拷贝:为了保证修改拷贝后的新对象不会对原对象产生影响,一般建议使用深拷贝。但如果数据量特别特别大,为了避免影响性能,建议使用浅拷贝。
最后,本文收录于个人语雀知识库: 我所理解的后端技术,欢迎来访。
文档信息
- 本文作者:Planeswalker23
- 本文链接:https://planeswalker23.github.io/2021/06/29/%E6%88%91%E6%89%80%E7%90%86%E8%A7%A3%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%B3%BB%E5%88%97-%E7%AC%AC4%E7%AF%87-%E5%8E%9F%E5%9E%8B%E6%A8%A1%E5%BC%8F%E4%B8%8D%E5%B0%B1%E6%98%AF%E5%85%8B%E9%9A%86-%E8%BF%99%E4%B9%88%E7%AE%80%E5%8D%95%E6%88%91%E4%B9%9F%E4%BC%9A/
- 版权声明:本作品系原创,作者保留所有权利,未经作者允许,禁止转载和演绎。