1 概述
原型模式比較好理解,即以某個對象爲原型,創建該對象的副本。我們可以不用知道對象內部的屬性以及內部的狀態,是迪米特法則的很好體現。
2 原型模式
原型模式一般用在較爲複雜對象的創建,並且希望保留對象所持有的狀態。Java
對這種對象的創建方式也是提供了原生的支持——Object.clone()
方法。
public class Object {
protected native Object clone() throws CloneNotSupportedException;
}
因爲Object
是所有類的父類,所以Java
中所有的類都可以重寫clone()
方法。當然Object
類中的clone()
方法也提供了native
實現,可以直接通過super.clone()
調用,前提是對象需要實現Cloneable
接口,否則會報java.lang.CloneNotSupportedException
的錯誤。
3 案例
3.1 淺拷貝
看下面一個例子,用Object
類中的clone()
方法實現複製:
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Job job = new Job("engineer", "Java coding");
Person nightfield = new Person(181, 65, job);
// 複製一個對象
Person nightfieldCopy = nightfield.clone();
System.out.format("Height of copied person: %s\n", nightfieldCopy.getHeight());
System.out.format("Weight of copied person: %s\n", nightfieldCopy.getWeight());
System.out.format("Job of copied person: %s\n", nightfieldCopy.getJob());
// 給原對象重新賦值
nightfield.setHeight(160);
nightfield.setWeight(80);
nightfield.getJob().setJobDescription("Python coding");
System.out.format("Height of copied person: %s\n", nightfieldCopy.getHeight());
System.out.format("Weight of copied person: %s\n", nightfieldCopy.getWeight());
System.out.format("Job of copied person: %s\n", nightfieldCopy.getJob());
}
}
public class Person implements Cloneable {
private double height;// cm
private double weight;// kg
private Job job;
Person(double height, double weight, Job job) {
this.height = height;
this.weight = weight;
this.job = job;
}
public double getHeight() { return height; }
public void setHeight(double height) { this.height = height; }
public double getWeight() { return weight; }
public void setWeight(double weight) { this.weight = weight; }
public Job getJob() { return job; }
public void setJob(Job job) { this.job = job; }
[@Override](https://my.oschina.net/u/1162528)
protected Person clone() throws CloneNotSupportedException {
// 直接調用Object的clone()方法
return (Person)super.clone();
}
}
public class Job {
private String jobName;
private String jobDescription;
Job(String jobName, String jobDescription) {
this.jobName = jobName;
this.jobDescription = jobDescription;
}
public void setJobDescription(String description) {this.jobDescription = description }
public String getJobName() { return jobName; }
public String getJobDescription() { return jobDescription; }
[@Override](https://my.oschina.net/u/1162528)
public String toString() {
return "Job{" +
"jobName='" + jobName + '\'' +
", jobDescription='" + jobDescription + '\'' +
'}';
}
}
輸出:
Height of copied person: 181.0
Weight of copied person: 65.0
Job of copied person: Job{jobName='engineer', jobDescription='Java coding'}
Height of copied person: 181.0
Weight of copied person: 65.0
Job of copied person: Job{jobName='engineer', jobDescription='Python coding'}
可以看到,對於基本類型的修改,不會影響副本類;但是對引用對象的修改,會導致副本類也跟着改變。這說明Object
類中的clone()
方法的默認實現是一個淺拷貝,也就是說副本內部的對象並沒有真正複製,而只是複製了引用。
這種機制在很多情況下會導致問題,有沒有乾淨利落的複製方式呢?於是有了深拷貝。
3.2 深拷貝
還是剛纔的例子,我們通過在clone()
方法裏手動創建對象並賦值的方式,可以實現深拷貝,下面只給出Person
類的代碼。
public class Person {
private double height;// cm
private double weight;// kg
private Job job;
Person(double height, double weight, Job job) {
this.height = height;
this.weight = weight;
this.job = job;
}
public double getHeight() { return height; }
public void setHeight(double height) { this.height = height; }
public double getWeight() { return weight; }
public void setWeight(double weight) { this.weight = weight; }
public Job getJob() { return job; }
public void setJob(Job job) { this.job = job; }
[@Override](https://my.oschina.net/u/1162528)
protected Person clone() {
Job clonedJob = new Job(job.getJobName(), job.getJobDescription());
Person person = new Person(height, weight, clonedJob);
return person;
}
}
輸出:
Height of copied person: 181.0
Weight of copied person: 65.0
Job of copied person: Job{jobName='engineer', jobDescription='Java coding'}
Height of copied person: 181.0
Weight of copied person: 65.0
Job of copied person: Job{jobName='engineer', jobDescription='Java coding'}
對原類的修改,並不影響副本類的值,說明此複製是連同類裏面的對象也一起復制了。
上面這種深拷貝實現方式,因爲需要我們在clone()
方法裏創建對象並賦值,所以要求我們對類的結構以及屬性非常瞭解。當類比較多或者類的層級很多的時候,會變得很複雜,而且每當該類新增修改屬性,都需要修改clone()
方法,顯然不符合開閉原則。
第二種深拷貝的方式,是利用JSON
序列化。通過將對象轉化成JSON
字符串,再轉回對象的方式,實現深拷貝,下面只給出關鍵代碼:
public class Person {
[@Override](https://my.oschina.net/u/1162528)
protected Person clone() {
Gson gson = new GsonBuilder().create();
// 將對象轉成JSON String
String jsonPerson = gson.toJson(this);
// 將JSON轉化回對象
Person newPerson = gson.fromJson(jsonPerson, this.getClass());
return newPerson;
}
}
通過Gson
(或者Jackson
)包的幫助,我們可以做到對Person
對象的深拷貝。這種方式的好處是很通用,我們只依賴於JSON
的類庫(一般所有工程都會有)。不過因爲它涉及到JSON
的轉化,所以拷貝效率不是很理想。
第三種深拷貝的方法,是通過Serializable
接口提供的序列化與反序列化:先將對象轉化成二進制流,然後再轉回原對象類型。下面只給出關鍵代碼:
public class Person implements Serializable {
[@Override](https://my.oschina.net/u/1162528)
protected DeepPerson serializeClone() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
// 將對象轉成二進制流
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
// 從二進制中讀取對象
return (DeepPerson) ois.readObject();
} catch (IOException e) {
logger.error("Error cloning object", e)
return null;
} catch (ClassNotFoundException e) {
logger.error("Error cloning object", e)
return null;
}
}
}
public class Job implements Serializable {
...
}
流序列化的方式,效率比JSON
序列化高很多,應該說是最理想的,像Apache
的SerializationUtils.clone()就是用的這種方法。不過唯一的限制是,目標對象,以及目標對象引用鏈下的所有對象,都必須實現Serializable
接口(如上例中的Person
類和Job
類),否則無法成功序列化。
4 總結
原型模式是一種比較簡單的創建模式,可以實現對象的複製。應用過程中,應當根據實際情況選擇對應的最適合的複製方式。