原型模式:
优点:
- 在创建对象的过程比较复杂的时候,可以使用原型模式来简化对象的创造过程,同时提高效率。
- 无需重新初始化对象,而动态的获取对象的运行时状态。
- 如果被克隆对象字段发生变化,无需修改代码。
- 在深拷贝时可能需要较为复杂的代码。
缺点
需要为每个类配置一个克隆的方法,对原有类进行改造的时候,违反了开闭原则。
需求
复制多个对象
使用原型模式前的代码:
被克隆的类
import lombok.Data;
/**
* @ClassName: Sheep
* @Description: 羊类
**/
@Data
public class Sheep {
//名称
private String name;
//年龄
private int age;
//颜色
private String color;
public Sheep(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
public Sheep() {
}
}
客户端:
public class Client {
public static void main(String[] args) {
Sheep sheep = new Sheep("tom",2,"white");
//没有使用原型模式时, 创建对象的方式需要去获取克隆对象的字段,如果克隆类字段有所改变,那我们的代码也需要进行改变
Sheep sheep1 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
Sheep sheep3 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
Sheep sheep4 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
System.out.println(sheep.toString());
System.out.println(sheep1.toString());
System.out.println(sheep2.toString());
System.out.println(sheep3.toString());
System.out.println(sheep4.toString());
}
}
我们看下执行结果:
Sheep(name=tom, age=2, color=white)
Sheep(name=tom, age=2, color=white)
Sheep(name=tom, age=2, color=white)
Sheep(name=tom, age=2, color=white)
Sheep(name=tom, age=2, color=white)
可以看到在没有使用原型模式的时候,我们需要手动的去初始化对象,并且如果被克隆的对象增加了新的字段,我们的代码也会受到影响。
接下来使用原型模式:
两种方式实现:
- 被克隆类实现Cloneable接口,重写clone方法。
- 通过序列化的方式。
第一种方式代码如下:
@NoArgsConstructor
public class Sheep implements Cloneable {
//名称
private String name;
//年龄
private int age;
//颜色
private String color;
//重写克隆方法,由于当前类没有引用类型变量,我们直接使用父类的代码就可以
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public Sheep(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
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 String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\''
'}';
}
}
客户端代码:
public class Client {
public static void main(String[] args) {
Sheep sheep = new Sheep("tom",2,"blue");
sheep.dog = new Dog("边牧", 3);
try {
Sheep sheep1 = (Sheep) sheep.clone();
Sheep sheep2 = (Sheep) sheep.clone();
Sheep sheep3 = (Sheep) sheep.clone();
Sheep sheep4 = (Sheep) sheep.clone();
System.out.println("sheep1:"+sheep1.toString());
System.out.println("sheep2:"+sheep2.toString());
System.out.println("sheep3:"+sheep3.toString());
System.out.println("sheep4:"+sheep4.toString());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
执行结果:
sheep1:Sheep{name='tom', age=2, color='blue'}
sheep2:Sheep{name='tom', age=2, color='blue'}
sheep3:Sheep{name='tom', age=2, color='blue'}
sheep4:Sheep{name='tom', age=2, color='blue'}
可以看到使用原型模式的第一种实现方式,也可以达到目的。并且我们无需手动的初始化对象。在被克隆类增加新的字段后,我们也无需改动。但是现在的代码属于是浅拷贝,如果被克隆的类中存在引用类型字段,这个时候,我们新克隆出来的对象的引用对象只会指向被克隆对象的地址,而不是创建新的引用类型对象。
看代码:
我们的被克隆类新增了Dog类。
public class Sheep implements Cloneable {
//名称
private String name;
//年龄
private int age;
//颜色
private String color;
//牧羊犬
public Dog dog;
这个时候再执行我们的客户端代码:
sheep:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@511d50c0}1360875712
sheep1:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@511d50c0}1360875712
sheep2:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@511d50c0}1360875712
sheep3:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@511d50c0}1360875712
sheep4:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@511d50c0}1360875712
根据结果可以看到,新创建的sheep1、sheep2、sheep3、sheep4的dog的hashcode地址一致,并没有创建新的Dog对象。而是将引用指向了sheep的dog。
修改我们的Sheep类的clone方法,使我们可以达到深拷贝。
@Override
protected Object clone() throws CloneNotSupportedException {
Object sheep = null;
sheep = super.clone();
Sheep sheepNew = (Sheep) sheep;
sheepNew.dog = (Dog) dog.clone();
return sheepNew;
}
再次执行客户端代码得到结果:
sheep:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@511d50c0}1360875712
sheep1:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@60e53b93}1625635731
sheep2:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@5e2de80c}1580066828
sheep3:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@1d44bcfa}491044090
sheep4:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@266474c2}644117698
此时可以看到我们在克隆对象的同时也克隆了他的引用类型字段。
接下来我们看第二种方式:
Sheep、Dog实现Serializable接口并编写新的克隆方法
public Object deepClone(){
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
try{
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);//当前这个对象以对象流的方式输出
//反序列化
bais = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bais);
Sheep sheep = (Sheep) ois.readObject();
return sheep;
}catch(Exception e){
}
return null;
}
执行下客户端代码:
public class Client {
public static void main(String[] args) {
Sheep sheep = new Sheep("tom",2,"blue");
sheep.dog = new Dog("边牧", 3);
Sheep sheep1 = (Sheep) sheep.deepClone();
Sheep sheep2 = (Sheep) sheep.deepClone();
Sheep sheep3 = (Sheep) sheep.deepClone();
Sheep sheep4 = (Sheep) sheep.deepClone();
System.out.println("sheep:"+sheep.toString()+sheep.dog.hashCode());
System.out.println("sheep1:"+sheep1.toString()+sheep1.dog.hashCode());
System.out.println("sheep2:"+sheep2.toString()+sheep2.dog.hashCode());
System.out.println("sheep3:"+sheep3.toString()+sheep3.dog.hashCode());
System.out.println("sheep4:"+sheep4.toString()+sheep4.dog.hashCode());
}
}
打印结果:
sheep:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@d716361}225534817
sheep1:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@27973e9b}664223387
sheep2:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@312b1dae}824909230
sheep3:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@7530d0a}122883338
sheep4:Sheep{name='tom', age=2, color='blue', dog=com.yxm.prototype.after.Dog@27bc2616}666641942
可以看到第二种方式也可以达到我们想要的结果,并且可以解决浅拷贝的问题。而且在被克隆类新增引用类型字段后,我们无需修改代码。比第一种方式更好一些。
需要注意的:
在编写案例的时候,由于博主使用@Data注解,导致其重写了类的Equals和HashCode方法,
导致在输出结果的时候,使用了原型模式,打印的Dog的hashcode也一致。
导致博主反复的寻找问题,一致以为是clone方法出现问题。
记录下来,防止其他小伙伴遇到我遇到的这个坑。