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 总结
原型模式是一种比较简单的创建模式,可以实现对象的复制。应用过程中,应当根据实际情况选择对应的最适合的复制方式。
来源:oschina
链接:https://my.oschina.net/u/2411391/blog/3212079