设计模式之原型模式
前言
在现实世界中,我们通常会感觉到分身乏术。要是自己有分身那该多好啊,一个用来工作,一个用来看电视,一个用来玩游戏(无意中透露了自己单身狗的身份-。-),其实就是克隆,这种技术存在着很大的弊端,所以现在是禁止使用的。但是这种复制技术在java的世界里早已出现,就是原型模式
什么是原型模式
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
uml类图
原型模式是设计模式中最简单的,没有之一。因为它的核心就是一个clone方法,通过这个方法完成对象的克隆。java提供了cloneable接口来标示这个对象是有可能被克隆的,这个接口只具有标记作用,在jvm中只有具有这个标记的对象才有可能被克隆。有可能克隆变成可以被克隆,就需要我们重写clone方法
浅拷贝
public class Person implements Cloneable{ public Person() { System.out.println("构造函数执行了"); } private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override protected Person clone() throws CloneNotSupportedException { return (Person) super.clone(); } }
public class Client { public static void main(String[] args) throws CloneNotSupportedException { Person person = new Person(); person.setName("bin"); Person person1=person.clone(); person1.setName("123"); System.out.println(person.getName()+"---"+person1.getName()); } } 测试结果: 构造函数执行了 bin---123
通过以上例子可以得知,实现cloneable接口,复写clone方法,即可对对象进行复制。有一个问题clone方法是怎么创建对象的?可以看到,构造函数只执行了一次,这就说明clone方法是不会调用构造函数的,它其实是内存二进制流的拷贝,比直接new对象性能要好很多。
标题是浅拷贝,那到底什么是浅拷贝呢?先不要着急,我们来改动下例子:
public class Person implements Cloneable{ private List<String> valueList = new ArrayList<>(); public void setValue(){ valueList.add("1"); } public List<String> getValue(){ return valueList; } @Override protected Person clone() throws CloneNotSupportedException { return (Person) super.clone(); } }
public class Client { public static void main(String[] args) throws CloneNotSupportedException { Person person = new Person(); person.setValue(); Person person1=person.clone(); person1.setValue(); System.out.println(person1.getValue()); } } 测试过程: 构造函数执行了 [1, 1]
或许你会很费解明明进行了拷贝,应该只会打印一个“1”啊,,为什么person1.getValue()会打印出两个“1”呢?因为java做了一个偷懒的拷贝动作,Object提供的clone方法只拷贝本对象,对其引用对象和内部数组都不拷贝,只是将地址拷贝过来用,这种拷贝方式就是浅拷贝。但是String对象例外,因为java本就希望将String看成基本数据类型,它没有clone方法,并且它的处理机制非常特殊,通过字符串池在内存中创建新的字符串,我们只要把其看待成基本数据类型就可以了。
深拷贝
当拷贝的对象中有引用对象或者数组时,我们通过浅拷贝获得复制对象是不安全的。怎么解决呢?我们可以通过深拷贝来解决这个问题,下面继续改造例子学习深拷贝:
public class Person implements Cloneable{ private ArrayList<String> valueList = new ArrayList<>(); public void setValue(){ valueList.add("1"); } public List<String> getValue(){ return valueList; } @Override protected Person clone() throws CloneNotSupportedException { Person person=(Person) super.clone(); person.valueList= (ArrayList<String>) this.valueList.clone(); return person; } }
public class Client { public static void main(String[] args) throws CloneNotSupportedException { Person person = new Person(); person.setValue(); Person person1=person.clone(); person1.setValue(); System.out.println(person.getValue()); System.out.println(person1.getValue()); } } 测试结果: person:[1] person1[1, 1]
通过对引用对象和数组的进行独立的拷贝,就完成了深拷贝,每个person对象都会有自己的valueList。
clone和final
对象的clone和对象内的final是互相冲突的,下面我们来看个图片:
当我们将valueList增加final关键字,对其clone编译会报错,解决办法就是不要加final关键字(感觉像是废话...)
总结
原型模式和现实世界中说的克隆基本一样。原型模式是内存二进制流的拷贝,比new对象性能高很多,尤其是当创建这个对象需要数据库或者其他硬件资源时尤为明显。另外我们需要注意的就是当复制的对象存在引用对象和数组时,要根据实际业务选择深拷贝还是浅拷贝。