所谓的原型模式,无非就是从一个对象再创建另一个可定制的对象,而且不需要知道任何创建的细节。所谓的原型模式,其实质就是编程需要中的克隆技术,以某个对象为原型,复制出新的对象。只是需要注意深复制与浅复制的问题。
其实关于原型模式,大话设计模式这本书中的相关实例已经说明的比较通俗了,这里只是重新梳理深复制与浅复制的区别和实例
Java中针对基本数据类型的成员变量,在浅复制的时候是完全复制一份给新对象。针对引用数据类型的变量而是直接拷贝引用,这样两个对象其实操作的是同一个对象。
浅复制实例代码:
public class ShallowCopy {
public static void main(String[] args) {
Age a=new Age(20);
Student stu1=new Student("TestMan",a,175);
//通过调用重写后的clone方法进行浅拷贝
Student stu2=(Student)stu1.clone();
System.out.println(stu1.toString());
System.out.println(stu2.toString());
//尝试修改stu1中的各属性,观察stu2的属性有没有变化
stu1.setName("大傻子");
//改变age这个引用类型的成员变量的值
a.setAge(99);
//stu1.setaAge(new Age(99)); 使用这种方式修改age属性值的话,stu2是不会跟着改变的。因为创建了一个新的Age类对象而不是改变原对象的实例值
stu1.setLength(216);
System.out.println(stu1.toString());
System.out.println(stu2.toString());
}
}
/*
* 创建年龄类
*/
class Age{
//年龄类的成员变量(属性)
private int age;
//构造方法
public Age(int age) {
this.age=age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return this.age+"";
}
}
/*
* 创建学生类
*/
class Student implements Cloneable{
//学生类的成员变量(属性),其中一个属性为类的对象
private String name;
private Age aage;
private int length;
//构造方法,其中一个参数为另一个类的对象
public Student(String name,Age a,int length) {
this.name=name;
this.aage=a;
this.length=length;
}
//eclipe中alt+shift+s自动添加所有的set和get方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Age getaAge() {
return this.aage;
}
public void setaAge(Age age) {
this.aage=age;
}
public int getLength() {
return this.length;
}
public void setLength(int length) {
this.length=length;
}
//设置输出的字符串形式
public String toString() {
return "姓名是: "+this.getName()+", 年龄为: "+this.getaAge().toString()+", 长度是: "+this.getLength();
}
//重写Object类的clone方法
public Object clone() {
Object obj=null;
//调用Object类的clone方法,返回一个Object实例
try {
obj= super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return obj;
}
}
上述代码是从这篇博客中找到的,只是原博客中代码运行实例不正确,其中列举了常见的几种数据类型,运行结果如下
上述代码其实就是将Age这个属性变成了一个对象,在进行学生对象复制的过程中,完成了由于Age是一个对象,进行的是浅复制,后面修改其中一个对象的Age值,最后两者的值会被修改,运行结果的最后一行中的Age就能说明这一点。
这里重点说一下String类型,String在Java中不是基础数据类型,是引用数据类型,但是却是存放在常量池中的引用数据类型,上述实例在修改stu1中的name属性的时候,并不是修改了这个数据的值,而是把这个数据的引用从指向”TestMan“这个常量改为了指向”大傻子“这个常量。在这种情况下,另一个对象的name属性值仍然指向”TestMan“不会受到影响。
深复制就是相对浅复制而言,针对引用类型的属性进行完全的拷贝,而不仅仅只是将引用地址进行复制。
完成深复制一般有两种方式:一种是在复写clone方法的时候,调用引用对象的clone方法,另一种是利用序列化操作完成
public class DeepCopy {
public static void main(String[] args) {
Age a=new Age(20);
Student stu1=new Student("TestMan",a,175);
//通过调用重写后的clone方法进行浅拷贝
Student stu2=(Student)stu1.clone();
System.out.println(stu1.toString());
System.out.println(stu2.toString());
System.out.println();
//尝试修改stu1中的各属性,观察stu2的属性有没有变化
stu1.setName("大傻子");
//改变age这个引用类型的成员变量的值
a.setAge(99);
//stu1.setaAge(new Age(99)); 使用这种方式修改age属性值的话,stu2是不会跟着改变的。因为创建了一个新的Age类对象而不是改变原对象的实例值
stu1.setLength(216);
System.out.println(stu1.toString());
System.out.println(stu2.toString());
}
}
/*
* 创建年龄类
*/
class Age implements Cloneable{
//年龄类的成员变量(属性)
private int age;
//构造方法
public Age(int age) {
this.age=age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return this.age+"";
}
//重写Object的clone方法
public Object clone() {
Object obj=null;
try {
obj=super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return obj;
}
}
/*
* 创建学生类
*/
class Student implements Cloneable{
//学生类的成员变量(属性),其中一个属性为类的对象
private String name;
private Age aage;
private int length;
//构造方法,其中一个参数为另一个类的对象
public Student(String name,Age a,int length) {
this.name=name;
this.aage=a;
this.length=length;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Age getaAge() {
return this.aage;
}
public void setaAge(Age age) {
this.aage=age;
}
public int getLength() {
return this.length;
}
public void setLength(int length) {
this.length=length;
}
public String toString() {
return "姓名是: "+this.getName()+", 年龄为: "+this.getaAge().toString()+", 长度是: "+this.getLength();
}
//重写Object类的clone方法
public Object clone() {
Object obj=null;
//调用Object类的clone方法——浅拷贝
try {
obj= super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
//调用Age类的clone方法进行深拷贝
//先将obj转化为学生类实例
Student stu=(Student)obj;
//这里是利用clone方法实现深度复制的关键所在,调用对象属性的clone方法完成深度复制
//学生类实例的Age对象属性,调用其clone方法进行拷贝
stu.age=(Age)stu.getaAge().clone();
return obj;
}
}
运行结果如下:
如果引用类型的属性过多,需要一个一个调用clone方法,会比较麻烦,因此可以通过序列化来实现深复制,具体实例如下:
public class DeepCopyBySerialization {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Age a=new Age(20);
Student stu1=new Student("摇头耶稣",a,175);
//通过序列化方法实现深拷贝
ByteArrayOutputStream bos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bos);
oos.writeObject(stu1);
oos.flush();
ObjectInputStream ois=new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
Student stu2=(Student)ois.readObject();
System.out.println(stu1.toString());
System.out.println(stu2.toString());
System.out.println();
//尝试修改stu1中的各属性,观察stu2的属性有没有变化
stu1.setName("大傻子");
//改变age这个引用类型的成员变量的值
a.setAge(99);
stu1.setLength(216);
System.out.println(stu1.toString());
System.out.println(stu2.toString());
}
}
/*
* 创建年龄类
*/
class Age implements Serializable {
//年龄类的成员变量(属性)
private int age;
//构造方法
public Age(int age) {
this.age=age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return this.age+"";
}
}
/*
* 创建学生类
*/
class Student implements Serializable{
//学生类的成员变量(属性),其中一个属性为类的对象
private String name;
private Age aage;
private int length;
//构造方法,其中一个参数为另一个类的对象
public Student(String name,Age a,int length) {
this.name=name;
this.aage=a;
this.length=length;
}
//eclipe中alt+shift+s自动添加所有的set和get方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Age getaAge() {
return this.aage;
}
public void setaAge(Age age) {
this.aage=age;
}
public int getLength() {
return this.length;
}
public void setLength(int length) {
this.length=length;
}
//设置输出的字符串形式
public String toString() {
return "姓名是: "+this.getName()+", 年龄为: "+this.getaAge().toString()+", 长度是: "+this.getLength();
}
}
实例运行结果同上面一样,这里不再列出
关于原型模式,其本身类结构图,在大话设计模式一书中有详细的介绍,clone方法是JDK为我们提供的实现原型模式的一种简单的方式,只是我们在使用的时候需要注意浅复制和深复制的区别。