原型模式
一. 说明
与单例模式相对的就是原型模式,原型模式是为了重复创建对象而产生的,同样也属于创建型模式。
原型模式实现了一个原型接口,用于创建对象的克隆体,提高直接创建对象的性能,这种设计模式在平常开发中应用较少,在java中主要依赖jdk的Cloneable接口重写 clone()实现对象拷贝。
二.应用场景
- 投简历时简历需要复制多份针对不同公司做针对性修改
- 考试题库需要完整复制给每一个考生,考生的题库相同而答案不同
- 创建对象如果需要非常繁琐的数据准备我们可以直接用原型模式拷贝对象
三.代码示例
我们以简历的复制为例,讲述原型模式的用法。
首先我们创建一个简历对象Resume,让它实现Cloneable接口重写clone方法。
@Data
public class Resume implements Cloneable {
private String name;
private String sex;
private int age;
private String experience;
public Resume(String name) {
this.name = name;
}
@Override
protected Resume clone() {
try {
return (Resume) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
public void display() {
System.out.println(name + " " + sex + " " + age);
System.out.println(experience);
}
}
测试输出结果
public static void main(String[] args) {
Resume resume1 = new Resume("张三");
resume1.setSex("男");
resume1.setAge(22);
resume1.setExperience("2015-2018就职于某大型公司");
Resume resume2 = resume1.clone();
resume2.setExperience("2012-2014就职于某大型公司");
Resume resume3 = resume1.clone();
resume3.setAge(26);
resume1.display();
resume2.display();
resume3.display();
}
这么看起来,使用原型模式克隆对象要比new一个对象更加方便了,既隐藏了对象创建的细节,又将性能大大的提高了。
这里我们需要注意一个细节问题,super.clone()如果字段是值类型的,则对字段进行复制;如果字段是引用类型的,则复制引用而不是复制引用的值对象,因此原始对象和克隆对象引用了同一对象。
我们将Resume类中的experience更改为Experience对象来说明这个问题。
@Data
public class Experience {
private String time;
private String company;
@Override
public String toString() {
return time + "就职于" + company;
}
}
@Data
public class Resume implements Cloneable {
private String name;
private String sex;
private int age;
private Experience experience;
public Resume(String name) {
this.name = name;
this.experience = new Experience();
}
@Override
protected Resume clone() {
try {
return (Resume) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
public void setWork(String time, String company){
this.experience.setTime(time);
this.experience.setCompany(company);
}
public void display() {
System.out.println(name + " " + sex + " " + age);
System.out.println(experience);
}
}
测试输出结果
public static void main(String[] args) {
Resume resume1 = new Resume("张三");
resume1.setSex("男");
resume1.setAge(22);
resume1.setWork("2012-2015", "A公司");
Resume resume2 = resume1.clone();
resume2.setWork("2015-2018", "B公司");
Resume resume3 = resume1.clone();
resume3.setAge(26);
resume3.setWork("2018-2019", "C公司");
resume1.display();
resume2.display();
resume3.display();
}
这里我们发现,当Resume中存在引用对象Experience时,在克隆对象中一旦更改Experience中的值时,原始对象和其他克隆对象对应的值都变更了,这个现象就叫做浅克隆
。浅克隆直白的说就是克隆对象所有的变量都与原始对象一致,对象中的引用也是指向原始对象的引用。
理所当然的,有浅克隆就有深克隆,当一个对象中存在引用对象时,我们克隆这个对象就需要使用深克隆
,在深克隆中,对象的所有引用对象都需要实现Cloneable接口重写clone(),并且在对象重写clone()时也要将引用属性同时调用clone()。
我们以下进行深克隆的代码改造。
@Data
public class Experience implements Cloneable {
private String time;
private String company;
@Override
public String toString() {
return time + "就职于" + company;
}
@Override
protected Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
@Data
public class Resume implements Cloneable {
private String name;
private String sex;
private int age;
private Experience experience;
public Resume(String name) {
this.name = name;
this.experience = new Experience();
}
@Override
protected Resume clone() {
try {
Resume resume = (Resume) super.clone();
resume.experience = (Experience) this.experience.clone();
return resume;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
public void setWork(String time, String company){
this.experience.setTime(time);
this.experience.setCompany(company);
}
public void display() {
System.out.println(name + " " + sex + " " + age);
System.out.println(experience);
}
}
用相同测试代码运行打印输入结果
此时,结果就是正确的。
四. 总结
原型模式虽然在工作中使用不太频繁,但我们需要知道java克隆的用法,区分浅克隆和深克隆,否则在软件开发中容易造成不必要的隐藏式bug。