一、定义
原型模式(Prototype pattern)属于创造性设计模式,是指原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
单例模式多次请求后得到的实例其实都只有一个,但是原型模式每次请求都会得到一个新的实例,只不过这个新的实例不是new出来的,而是原先的实例clone出来的。
二、适用场景
- 创建的对象实例较为复杂,如构造函数中含有多个参数,通过new生成对象实例花费高。
- 解耦框架与类,不通过具体类名,而是预先的"注册"一个"原型"实例,之后通过复制这个原型实例来生成新的实例。
- 原型模式一般一般与工厂模式结合使用,通过clone(),提供实例
❓Q: 为什么clone会比new的效率高❓
package com.selenium.demo;
public class AA implements Cloneable {
public AA() {
System.out.println("构造函数被调用");
}
public static void main(String[] args) throws CloneNotSupportedException {
AA aa = new AA();
AA bb = (AA) aa.clone();
}
}
可以看出只有在new AA()的时候调用了一次构造方法,clone()的时候并没有调用构造方法.
❗️A: 因为clone是native方法,直接在内存中复制数据块,操作二进制流,不会调用构造方法,少了对象的初始化的一些操作。❗️
三、主要角色
- Client(使用者): 负责使用复制实例的方法来复制实例
- Prototype(原型): 原型是接口类型,用来定义复制实例的方法
- ConcretePrototype(具体原型): 复制实例的方法的具体实现类
四、对象的深浅克隆
1.错误的克隆
刚学java的同学,可能想通过=不就实现了克隆吗?我真是个小天才!!!
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person{
private int id;
private String name;
private Dog dog;
public static void main(String[] args) throws Exception {
Dog dog = new Dog(1, "dog");
Person person = new Person(1, "person", dog);
Person person1 = person;
System.out.println("person==person1: " + (person == person1));
}
}
❌但是这样是错误的,=并没有重新开辟内存空间,而是将新的引用指向了同一块内存空间而已.
person和person1的内存地址相同,说明两个引用指向了同一块内存空间.
2.浅克隆
java.lang.Object中有native clone()方法,而我们实现克隆只需要实现Cloneable接口。
Cloneable、Serializable这些接口都是空实现,作用都是起标识作用,标识这个对象可以被克隆、被序列化.
java默认的clone方法实现的是浅克隆,即:只克隆了基本数据类型,而不克隆引用类型
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person implements Cloneable {
private int id;
private String name;
private Dog dog;
@Override
protected Person clone() throws CloneNotSupportedException {
return ((Person) super.clone());
}
public static void main(String[] args) throws Exception {
Dog dog = new Dog(1, "dog");
Person person = new Person(1, "person", dog);
Person person2 = person.clone();
//浅克隆:克隆对象内的基本数据类型,引用类型不被克隆
System.out.println("person2==person: " + (person2 == person));
System.out.println("person2.dog==person.dog:" + (person2.dog == person.dog));
}
}
可以看到,person的实例不同了,但是它们的dog实例还是同一个实例.
3.深克隆
了解了浅克隆,很容易就知道深克隆的概念,即:既克隆了基本数据类型,也克隆了引用类型
方式一:通过将引用类型手动clone实现深克隆
- 将Dog也实现Cloneable接口
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dog implements Cloneable{
private int id;
private String name;
@Override
protected Dog clone() throws CloneNotSupportedException {
return (Dog) super.clone();
}
}
- 手动clone Dog
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person implements Cloneable, Serializable {
private int id;
private String name;
private Dog dog;
@Override
protected Person clone() throws CloneNotSupportedException {
return ((Person) super.clone());
}
public static void main(String[] args) throws Exception {
Dog dog = new Dog(1, "dog");
Person person = new Person(1, "person", dog);
//深克隆:1.将引用对象也克隆
Person person3 = person.clone();
//手动clone Dog
person3.setDog(dog.clone());
System.out.println("person3==person: " + (person3 == person));
System.out.println("person3.dog==person.dog:" + (person3.dog == person.dog));
}
}
可以看到,person和dog都不是原来的实例了,都被clone了.
方式二:通过序列化实现深克隆
方式一实现深克隆的方式很直观,但是弊端也很明显:如果含有多个引用类型,或者引用类型又有引用类型,就很麻烦
.
所以通过Serializable实现序列化(Andriod推荐使用Parcelable)的方式实现深克隆才是最常用的方式.
- Dog实现Serializable接口
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dog implements Serializable {
private int id;
private String name;
}
- Person实现Serializable接口
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person implements Serializable {
private int id;
private String name;
private Dog dog;
public static void main(String[] args) throws Exception {
Dog dog = new Dog(1, "dog");
Person person = new Person(1, "person", dog);
//深克隆:2.通过序列化的方式
//将序列化对象写入流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(bos);
objectOutputStream.writeObject(person);
//将序列化对象从流中反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(bis);
Person person4 = (Person) objectInputStream.readObject();
System.out.println("person4==person: " + (person4 == person));
System.out.println("person4.dog==person.dog:" + (person4.dog == person.dog));
}
}
可以看到,person和dog也都不是原来的实例了