源代码:https://gitee.com/AgentXiao/PrototypePattern
一、原理
1、如果通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
2、java中的克隆技术,以某个对象为原型,复制出新的对象,新的对象具备原型对象的特点
3、优势:效率高(直接克隆,避免了重新执行构造过程步骤) 。
4、克隆类似于new,但是不同于new。new创建新的对象属性采用的是默认值。克隆出的对象的属性值完全和原型对象相同,并且克隆出的新对象改变不会影响原型对象。
二、实现(浅克隆)
需要背克隆的类需要实现Cloneable接口并重写clone方法
/**
* @ClassName Sheep
* @Description 原型羊
* @Author xwd
* @Date 2018/10/18 16:50
*/
public class Sheep implements Cloneable{
private String name;//名字
private Date birthday;//生日
public Sheep() {
}
public Sheep(String name, Date birthday) {
this.name = name;
this.birthday = birthday;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone();
return obj;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
浅克隆分析
被复制的对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。也就是说name和birthday具有相同的值和引用。这就导致一个问题,一旦该引用改变了,克隆的对象也会变化。
/**
* @ClassName Client
* @Description 测试浅克隆
* @Author xwd
* @Date 2018/10/18 16:53
*/
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
String name = "多利";
Date date = new Date(5656565L);
Sheep sheep_1 = new Sheep(name,date);
System.out.println("原型对象:"+sheep_1);
System.out.println("原型对象的生日:"+sheep_1.getBirthday());
System.out.println("原型对象的名字:"+sheep_1.getName());
Sheep sheep_2 = (Sheep) sheep_1.clone();
System.out.println("克隆对象:"+sheep_2);
System.out.println("克隆对象的生日:"+sheep_2.getBirthday());
System.out.println("克隆对象的名字:"+sheep_2.getName());
date.setTime(45456464l);//改变引用的值,如果是同一个引用,两者应该一起变化
System.out.println("原型对象的生日:"+sheep_1.getBirthday());
System.out.println("克隆对象的生日:"+sheep_2.getBirthday());
}
}
控制台输出的信息为:
由此可见确实是同一引用。
三、深克隆的实现
方法1:要实现深克隆,我们需要把属性也克隆了。
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone();
//将属性也进行克隆实现深克隆
Sheep2 s = (Sheep2) obj;
s.birthday = (Date) this.birthday.clone();
return obj;
}
在允许上面的client时,控制台输出:
方法二:使用序列化和反序列化实现深克隆
前提是Sheep类是实现Serializable接口。
/**
* @ClassName TestDeepClone
* @Description 使用序列化和反序列化实现深克隆
* @Author xwd
* @Date 2018/10/18 21:41
*/
public class TestDeepClone {
public static void main(String[] args) throws IOException, ClassNotFoundException {
String name = "多利";
Date date = new Date(5656565L);
Sheep sheep_1 = new Sheep(name,date);
System.out.println("原型对象:"+sheep_1);
System.out.println("原型对象的生日:"+sheep_1.getBirthday());
System.out.println("原型对象的名字:"+sheep_1.getName()+"\r\n");
//序列化s1
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(sheep_1);
byte[] s1 = baos.toByteArray();
//反序列化s1,得到深克隆的对象s2
ByteArrayInputStream bais = new ByteArrayInputStream(s1);
ObjectInputStream ois = new ObjectInputStream(bais);
Sheep sheep_2 = (Sheep) ois.readObject();
System.out.println("通过序列化和反序列化得到的对象:"+sheep_2);
System.out.println("通过序列化和反序列化得到的对象的生日:"+sheep_2.getBirthday());
System.out.println("通过序列化和反序列化得到的对象的名字:"+sheep_2.getName()+"\r\n");
date.setTime(56564654564L);
System.out.println("原型对象的生日:"+sheep_1.getBirthday());
System.out.println("通过序列化和反序列化得到的对象的生日:"+sheep_2.getBirthday());
}
}
控制台的信息为:
四、效率测试
/**
* @ClassName TestSpeed
* @Description 效率测试
* @Author xwd
* @Date 2018/10/18 21:59
*/
public class TestSpeed {
public static void main(String[] args) throws CloneNotSupportedException {
buildByNew(1000);
buildByClone(1000);
}
public static void buildByNew(int size){
long start = System.currentTimeMillis();
for(int i=0;i<size;i++){
new Computer();
}
long end = System.currentTimeMillis();
System.out.println("通过new方式创建"+size+"个对象耗时:"+(end - start));
}
public static void buildByClone(int size) throws CloneNotSupportedException {
Computer computer = new Computer();
long start = System.currentTimeMillis();
for(int i=0;i<size;i++){
computer.clone();
}
long end = System.currentTimeMillis();
System.out.println("通过clone方式创建"+size+"个对象耗时:"+(end - start));
}
}
class Computer implements Cloneable{
public Computer(){
try {
Thread.sleep(5);//模拟通过new创建对象时的耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
结果如下(不同电脑不一样):
五、开发中的应用场景
原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。
• spring中bean的创建实际就是两种:单例模式和原型模式。(当然,原型模式需要和工厂模式搭配起来)