原型模式:用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。
原型模式其實就是從一個對象再創建另外一個可定製的對象,而且不需要知道任何的創建細節。
原型模式需要一個原型類,在Java中的Object類中有clone()方法,只需要實現Cloneable接口即可完成原型模式。
下面以寫簡歷的例子來說明原型模式:
簡歷包含姓名,年齡以及工作經驗信息
簡歷對象:
public class Resume implements Cloneable {
private String name;
private Integer age;
private WorkExperience workExperience;
public Resume(String name) {
this.name = name;
this.workExperience = new WorkExperience();
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public WorkExperience getWorkExperience() {
return workExperience;
}
public void setWorkExperience(String timeArea, String company) {
this.workExperience.setTimeArea(timeArea);
this.workExperience.setCompany(company);
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Resume{" +
"name='" + name + '\'' +
", age=" + age +
'}' +
"WorkExperience{" +
"timeArea='" + workExperience.getTimeArea() + '\'' +
", company=" + workExperience.getCompany() +
'}';
}
}
工作經驗對象:
public class WorkExperience {
private String timeArea;
private String company;
public String getTimeArea() {
return timeArea;
}
public void setTimeArea(String timeArea) {
this.timeArea = timeArea;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
}
原型模式測試類:
public class PrototypeTest {
public static void main(String[] args) {
Resume resume1 = new Resume("nss");
resume1.setAge(22);
resume1.setWorkExperience("xx1年-xx1年", "xx1公司");
try {
// 只需要調用clone()方法就可以實現新簡歷的生成,並且可以再修改新簡歷的細節
Resume resume2 = (Resume) resume1.clone();
// 對象複製後,引用指向新的對象
System.out.println(resume1.equals(resume2));
resume2.setAge(23);
resume2.setWorkExperience("xx2年-xx2年", "xx2公司");
System.out.println(resume1.toString());
System.out.println(resume2.toString());
// resume2更改age後resume1的age不變
System.out.println(resume1.getAge().equals(resume2.getAge()));
// resume2更改WorkExperience後resume1的WorkExperience改變
System.out.println(resume1.getWorkExperience().equals(resume2.getWorkExperience()));
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
Output:
false
Resume{name='nss', age=22}WorkExperience{timeArea='xx2年-xx2年', company=xx2公司}
Resume{name='nss', age=23}WorkExperience{timeArea='xx2年-xx2年', company=xx2公司}
false
true
Object類的clone方法:
Object類的clone()方法用來創建當前對象的淺表副本。該方法創建一個新對象,然後將當前對象的非靜態字段複製到該新對象。如果字段是值類型的,則對該字段進行逐位複製。如果該字段是引用類型,則複製引用但不復制引用的對象。因此,原始對象及其副本引用同一對象。
淺複製和深複製:
淺複製:被複制對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用都仍然指向原來的對象。
clone()方法是淺複製,對於值類型沒問題,對於引用類型,就只是複製了引用,對引用對象還是指向了原來的對象。
有時候我們需要把要複製的對象的所有引用對象都複製一變,這種方式叫做深複製。
深複製:深複製把引用對象的變量指向複製過的新對象,而不是原有的被引用的對象。
把上面的代碼改爲深複製,只需要讓WorkExperience實現Cloneable接口並重寫clone方法,再修改一下Resume類的clone方法即可:
WorkExperience改爲:
public class WorkExperience implements Cloneable {
private String timeArea;
private String company;
public String getTimeArea() {
return timeArea;
}
public void setTimeArea(String timeArea) {
this.timeArea = timeArea;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Resume類clone方法改爲:
/**
* 對clone方法來說如果字段是值類型的,則對該字段進行逐位複製,如果字段是引用類型,則複製引用但不復制引用的對象
* 因此,原本的對象及其副本引用的是同一對象。所以如果想讓複製的引用類型字段指向新的對象,需要將引用類型對象再複製一份
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() throws CloneNotSupportedException {
Resume resume = (Resume) super.clone();
resume.workExperience = (WorkExperience) workExperience.clone();
return resume;
}
在複製Resume類時,將WorkExperience類也複製一份即可。
再次運行測試類,結果爲:
false
Resume{name='nss', age=22}WorkExperience{timeArea='xx1年-xx1年', company=xx1公司}
Resume{name='nss', age=23}WorkExperience{timeArea='xx2年-xx2年', company=xx2公司}
false
false
總結:
使用原型模式的作用:
使用原型模式可以提高性能,因爲創建對象時,每new一次,就需要執行一次構造函數,如果構造函數的執行時間很長,那麼多次執行這個初始化操作就很低效。
一般在初始化信息不發生變化的情況下,克隆是最好的辦法。這既隱藏了對象創建的細節,又對性能是大大的提高。使用原型模式等於是不用重新初始化對象,而是動態的獲取對象運行時的狀態。