原型(Prototype)模式
问题的引入———简历类
定义一个简历类,必须要有姓名,可以设置性别和年龄,可以设置工作经历,最终需要三份简历。
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
Resume a=new Resume("大鸟");
a.setPersonalInfo("男", "29");
a.setWorkExperience("1998-2000", "XX公司");
Resume b=new Resume("大鸟");
b.setPersonalInfo("男", "29");
b.setWorkExperience("1998-2000", "XX公司");
Resume c=new Resume("大鸟");
c.setPersonalInfo("男", "29");
c.setWorkExperience("1998-2000", "XX公司");
a.display();
b.display();
c.display();
}
}
class Resume{
private String name;
private String sex;
private String age;
private String timeArea;
private String company;
public Resume(String name) {
this.name=name;
}
public void setPersonalInfo(String sex,String age) {
this.sex=sex;
this.age=age;
}
public void setWorkExperience(String timeArea,String company) {
this.timeArea=timeArea;
this.company =company;
}
public void display() {
System.out.println(name+" "+sex+" "+age);
System.out.println("工作经历:"+timeArea+" "+company);
}
}
分析:3份简历需要3次实例化,这样很麻烦,一旦出现错误就需要修改很多次。
原型模式的用意是:通过给出一个原型对象来指明所要创建的对象类型,然后用复制这个原型对象的办法创建出更多的同类型对象。
Java对原型模式的支持:
Java语言中已经提供了clone方法,定义在Object类中,需要实现克隆功能的类,只需要实现java.lang.Cloneable接口即可。
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
Resume a=new Resume("大鸟");
a.setPersonalInfo("男", "29");
a.setWorkExperience("1998-2000", "XX公司");
Resume b=a.clone();
b.setWorkExperience("1998-2006", "YY公司");
Resume c=a.clone();
c.setPersonalInfo("男", "24");
a.display();
b.display();
c.display();
}
}
class Resume implements Cloneable{
private String name;
private String sex;
private String age;
private String timeArea;
private String company;
public Resume(String name) {
this.name=name;
}
public void setPersonalInfo(String sex,String age) {
this.sex=sex;
this.age=age;
}
public void setWorkExperience(String timeArea,String company) {
this.timeArea=timeArea;
this.company =company;
}
public void display() {
System.out.println(name+" "+sex+" "+age);
System.out.println("工作经历:"+timeArea+" "+company);
}
public Resume clone() {
try {return (Resume)super.clone(); }
catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
优点
- 客户端的代码简洁多了,而且想改某份简历,只需要对这份简历做一定的修改就可以了,不会影响到其他简历,相同的部分就不用再重复了。
- 对性能有大的提高,原因在于:每new一次,都需耍执行一次构造函数,如杲构造函数的执行时间很长,那么多次的执行这个初始化操作就太低效。一般在初始化的信息不发生变化的情况下,克隆是最好的办法,这既隐藏了对象创建的细节,又对性能是大大的提高,它等于是不用重新初始化对象,而是动态地获得对象运行时的状态。
浅复制与深复制
上例中‘简历’对象里的数据类型String是值类型(值类型就是基本类型),克隆成功了。clone(方法是这样的:
- 如果是字段是值类型的,则对该字段执行逐位复制,
- 如果字段是引用类型,则复制引用但不复制引用的对象;因此原始对象及其副本引用同一对象。就是说如果‘简历’ 类当中本对象引用,那么引用的对象数据不会被克隆。
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
// TODO Auto-generated method stub
Resume a=new Resume("大鸟");
a.setPersonalInfo("男", "29");
a.setWorkExperience("1998-2000", "XX公司");
Resume b=a.clone();
b.setWorkExperience("1998-2006", "YY公司");
Resume c=a.clone();
c.setWorkExperience("1998-2003", "ZZ公司");
a.display();
b.display();
c.display();
}
}
class WorkExperience{
private String workDate;
private String company;
public String getWorkDate() {
return workDate;
}
public void setWorkDate(String workDate) {
this.workDate = workDate;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
}
class Resume implements Cloneable{
private String name;
private String sex;
private String age;
private WorkExperience work=new WorkExperience();
public Resume(String name) {
this.name=name;
}
public void setPersonalInfo(String sex,String age) {
this.sex=sex;
this.age=age;
}
public void setWorkExperience(String workDate,String company) {
work.setWorkDate(workDate);
work.setCompany(company);
}
public void display() {
System.out.println(name+" "+sex+" "+age);
System.out.println("工作经历:"+work.getWorkDate()+" "+work.getCompany());
}
public Resume clone() throws CloneNotSupportedException {
return (Resume) super.clone();
}
}
只修改了b和c的工作经历,最后都变成了最后修改的c的工作经历。
出错原因:
- 由于是浅复制,所以对于值类型,没什么问题,但是对于引用类型,就只是复制了引用,引用的还是原来的对象,所以就会出现给a、b、c三个引用设置“工作经历’,,却同时看到三个引用都是最后的设置是一样的,因为这三个引用都指向了最初的同一个对象。
- ‘浅复制’:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。
- 这里需要把要复制的对象所引用的对象都复制一遍。比如上例,希望a、b、c三个引用的对象都是不同的,称这种方式为‘深复制’ ,深复制把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象。
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
// TODO Auto-generated method stub
Resume a=new Resume("大鸟");
a.setPersonalInfo("男", "29");
a.setWorkExperience("1998-2000", "XX公司");
Resume b=a.clone();
b.setWorkExperience("1998-2006", "YY公司");
Resume c=a.clone();
c.setWorkExperience("1998-2003", "ZZ公司");
a.display();
b.display();
c.display();
}
}
class WorkExperience implements Cloneable{
private String workDate;
private String company;
public String getWorkDate() {
return workDate;
}
public void setWorkDate(String workDate) {
this.workDate = workDate;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
public WorkExperience clone() {
try {
return (WorkExperience)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();}
return null;
}
}
class Resume implements Cloneable{
private String name;
private String sex;
private String age;
private WorkExperience work=new WorkExperience();
public Resume(String name) {
this.name=name;
}
public void setPersonalInfo(String sex,String age) {
this.sex=sex;
this.age=age;
}
public void setWorkExperience(String workDate,String company) {
work.setWorkDate(workDate);
work.setCompany(company);
}
public void display() {
System.out.println(name+" "+sex+" "+age);
System.out.println("工作经历:"+work.getWorkDate()+" "+work.getCompany());
}
public Resume clone(){
Resume obj=new Resume(name);
obj.sex=this.sex;
obj.age=this.age;
obj.work=work.clone();
return obj;
}
}
修改了b和c的工作经历,显示的工作经历就各不相同。
大鸟 男 29
工作经历:1998-2000 XX公司
大鸟 男 29
工作经历:1998-2006 YY公司
大鸟 男 29
工作经历:1998-2003 ZZ公司
克隆的实现方法有两种:浅复制( shallow copy)与深复制(deepcopy)。
浅复制:只克隆按值传递的数据(比如基本数据类型.String类型)
深复制:除了浅拷贝要克隆的值外,还克隆引用类型(属性的类型也是对象)的数据。
需要注意的是执行深复制后,原来的对象和新创建的对象不会共享任何东西;改变一个对象对另外一个对象没有任何影响。