定义
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。
要明白原型模式就要知道浅拷贝和深拷贝,重写clone()方法默认是浅拷贝。深拷贝有两种方法:1.重写clone()方法;2:序列化对象。
知识点
- Cloneable接口/Object#clone方法
- 浅拷贝/深拷贝
- 序列化机制实现深拷贝
看一个需求
有一只小绵羊,现在要根据这只小绵羊克隆出2个相同的绵羊。
如果使用传统的方案的话,就是先new一个绵羊,然后在创建两个绵羊,属性取第一只绵羊的属性,代码类似下面这样:
Sheep s = new Sheep();
s.setName("小绵羊");
s.setAge(1);
s.setColor("白色");
Sheep s1 = new Sheep();
s1.setName(s.getName());
s1.setAge(s.getAge());
s1.setColor(s.getColor);
Sheep s2 = new Sheep();
s2.setName(s.getName());
s2.setAge(s.getAge());
s2.setColor(s.getColor);
分析:这种方法虽然也能实现需求,但是如果绵羊的属性过多的话,对象创建起来就比较复杂,效率比较低。而且如果绵羊再加一个或多个属性的话,代码改起来会很麻烦。下面就用原型模式来解决上述问题。
原型模式-浅拷贝
用到原型模式,就得用到克隆的方法,被克隆的对象要实现Cloneable并重新clone方法。代码如下
Sheep对象
package com.hupp.prototype;
public class Sheep implements Cloneable{
public Sheep(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
private String name;
private int age;
private String color;
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
"}--hashcode:"+this.hashCode();
}
@Override
protected Object clone(){
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
主函数
package com.hupp.prototype;
public class Client {
public static void main(String[] args) {
Sheep s = new Sheep("小绵羊",2,"白色");
Sheep s1 = (Sheep)s.clone();
Sheep s2 = (Sheep)s.clone();
System.out.println(s);
System.out.println(s1);
System.out.println(s2);
}
}
输出结果
分析:使用克隆方法,无论绵羊加多少属性代码都不需要再改动。假如这绵羊有个实体属性(玩具)的话,clone方法会把玩具对象也克隆吗,下面改下代码然后测试下。
增加一个玩具类:Toys
public class Toys {
public Toys(String toyName){this.toyName=toyName;}
private String toyName;
}
修改Sheet类
package com.hupp.prototype;
public class Sheep implements Cloneable {
public Sheep(String name, int age, String color, Toys toys) {
this.name = name;
this.age = age;
this.color = color;
this.toys = toys;
}
private String name;
private int age;
private String color;
private Toys toys;
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color +
"}-sheetHashCode:"+this.hashCode()+
"--toysHashCode:"+this.toys.hashCode();
}
@Override
protected Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
修改主函数
package com.hupp.prototype;
public class Client {
public static void main(String[] args) {
Toys toys = new Toys("硅胶娃娃");
Sheep s = new Sheep("小绵羊",2,"白色",toys);
Sheep s1 = (Sheep)s.clone();
Sheep s2 = (Sheep)s.clone();
System.out.println(s);
System.out.println(s1);
System.out.println(s2);
}
结果如下
分析:可以看到clone方法克隆了两个绵羊,却没有克隆它的玩具。三个绵羊玩的是同一个玩具,不干净也不安全。这是因为:浅拷贝对于基本数据类型及字符串的成员变量会直接复制给新的对象,而对于数组、类等引用类型的对象只会将该对象的引用地址拷贝给新的对象。所以被克隆的对象及克隆出的新对象对于引用类型的数据都是指向同一个引用,并没有创建新的对象。如果想要将引用类型的数据也创建一份新的数据的话需要用到深拷贝。
原型模式-深拷贝
深拷贝属于完全拷贝,无论是基数数据类型的成员还是引用数据类型的成员都会创建新的数据。深拷贝常用实现方式有两种:
- 重新clone方法的方式
- 通过对象序列化的方式
通过重写clone实现深拷贝
思路:将toys也实现clone()方法,然后在Sheet类重写clone方法时,对Toys属性调用它自己的clone方法,代码如下
修改后的Toys类
package com.hupp.prototype;
public class Toys implements Cloneable{
public Toys(String toyName){this.toyName=toyName;}
private String toyName;
@Override
protected Object clone(){
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
修改后的Sheet类,只修改类clone()方法
package com.hupp.prototype;
public class Sheep implements Cloneable {
public Sheep(String name, int age, String color, Toys toys) {
this.name = name;
this.age = age;
this.color = color;
this.toys = toys;
}
private String name;
private int age;
private String color;
private Toys toys;
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color +
"}-sheetHashCode:"+this.hashCode()+
"--toysHashCode:"+this.toys.hashCode();
}
@Override
protected Object clone() {
try {
Object deep = super.clone();
Sheep sheep = (Sheep)deep;
sheep.toys = (Toys)this.toys.clone();
return deep;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
结果如下
分析:从输出的toysHashCode可以看出,绵羊的玩具也被克隆了,这时候每只绵羊都有自己的玩具了,也不用三只绵羊玩一个玩具了。
通过序列化实现深拷贝【推荐】
对象要实现序列化操作需要实现序列化接口,Sheet和Toys类都需要实现Serializable接口,代码如下
修改后的Toys类
package com.hupp.prototype;
import java.io.Serializable;
public class Toys implements Cloneable, Serializable {
public Toys(String toyName){this.toyName=toyName;}
private String toyName;
@Override
protected Object clone(){
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
修改后的Sheet类,增加一个deepClone方法
package com.hupp.prototype;
import java.io.*;
public class Sheep implements Serializable,Cloneable {
public Sheep(String name, int age, String color, Toys toys) {
this.name = name;
this.age = age;
this.color = color;
this.toys = toys;
}
private String name;
private int age;
private String color;
private Toys toys;
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color +
"}-sheetHashCode:"+this.hashCode()+
"--toysHashCode:"+this.toys.hashCode();
}
//深拷贝 --方式1:重写clone()f方法
@Override
protected Object clone() {
try {
Object deep = super.clone();
Sheep sheep = (Sheep)deep;
sheep.toys = (Toys)this.toys.clone();
return deep;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
//深拷贝 --方式2:通过对象的序列化实现(推荐)
public Object deepClone(){
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
Sheep sheep = (Sheep) ois.readObject();
return sheep;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
修改主函数
package com.hupp.prototype;
public class Client {
public static void main(String[] args) {
Toys toys = new Toys("芭比娃娃");
Sheep s = new Sheep("小绵羊",2,"白色",toys);
Sheep s1 = (Sheep)s.deepClone();
Sheep s2 = (Sheep)s.deepClone();
System.out.println(s);
System.out.println(s1);
System.out.println(s2);
}
}
执行结果