创建者模式之原型模式
原型模式:Prototype原型模式是一种创建型设计模式,Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。
实际上就是通过对原对象的一个拷贝,作为一个新对象,改变原来对象的一些属性,得到新的对象的过程,使用的原理就是java中的clone方法(c#里面也有一个clone方法,其他语言不太懂。c/c++应该是一个内存拷贝吧)。
看下面一段代码:
声明一个Ship类作为我们可以colne的类,因此继承Cloneable接口。代码如下:
import java.io.Serializable; import java.util.Date; public class Ship implements Cloneable, Serializable { public String name; public String getName() { return name; } public void setName(String name) { this.name = name; } private Date birthDay; public Date getBirthDay() { return birthDay; } public void setBirthDay(Date birthDay) { this.birthDay = birthDay; } public Object clone() throws CloneNotSupportedException { Object o = super.clone(); return o; } public Ship(String name, Date birthDay){ super(); this.name = name; this.birthDay = birthDay; } }
然后看一下使用类里面如何调用:
import java.util.Date; public class Main { public static void main(String[] args) throws CloneNotSupportedException { Date date = new Date(123445543L); Ship s1 = new Ship("多利", date); System.out.println(s1); System.out.println(s1.getName() + ":" + s1.getBirthDay()); date.setTime(234243234L); Ship s2 = (Ship)s1.clone(); s2.setName("多利的儿子"); System.out.println(s2); System.out.println(s2.getName() + ":" + s2.getBirthDay()); } }
这样执行下来的结果是
原型模式.Ship@2a139a55 多利:Fri Jan 02 18:17:25 CST 1970 原型模式.Ship@70dea4e 多利的儿子:Sun Jan 04 01:04:03 CST 1970
说明已经不是一个类了,但是里面的参数是一样的(除了自己修改的),这样就能快速的得到一个新的对象,而不是通过new方法。
但是这里会有一个问题,就是浅拷贝和深拷贝的问题,这里是一个浅拷贝,也就是s1和s2使用的一个Date对象,他们复制的时候,复制的是引用,指向的地址还是一样的。如果调用改成这样:
1 public static void main(String[] args) throws CloneNotSupportedException { 2 Date date = new Date(123445543L); 3 Ship s1 = new Ship("多利", date); 4 Ship s2 = (Ship)s1.clone(); 5 6 date.setTime(234243234L); 7 System.out.println(s1); 8 System.out.println(s1.getName() + ":" + s1.getBirthDay()); 9 10 s2.setName("多利的儿子"); 11 System.out.println(s2); 12 System.out.println(s2.getName() + ":" + s2.getBirthDay()); 13 }
输出是这样的:
1 原型模式.Ship@2a139a55 2 多利:Sun Jan 04 01:04:03 CST 1970 3 原型模式.Ship@70dea4e 4 多利的儿子:Sun Jan 04 01:04:03 CST 1970
通过这两个调用就可以看出来这是一个浅拷贝。我们下面看一下深拷贝是这么完成的。
1 public Object clone() throws CloneNotSupportedException { 2 Object o = super.clone(); 3 4 //添加如下代码实现深复制(deep Clone) 5 Ship s = (Ship)o; 6 s.setBirthDay((Date)this.birthDay.clone());//把属性也进行克隆! 7 8 return o; 9 }
改变clone方法,添加了两行代码,再来看一下结果
1 原型模式.Ship@2a139a55 2 多利:Sun Jan 04 01:04:03 CST 1970 3 原型模式.Ship@70dea4e 4 多利的儿子:Fri Jan 02 18:17:25 CST 1970
两个时间已经不同了,说明在给s1设置时间的时候,没有对s2的Date对象改变,也就是s2和s1的Date指向的是不同的内容。这就实现了深拷贝。
其实关于深拷贝的方法还可以使用序列化来实现,要想让我们的类(Ship类)可以序列化,必须继承Serializable接口。我们来看一下
1 public class Main2 { 2 public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException { 3 Date date = new Date(123445543L); 4 Ship s1 = new Ship("多利", date); 5 6 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 7 ObjectOutputStream oos = new ObjectOutputStream(bos); 8 oos.writeObject(s1); 9 byte[] bytes = bos.toByteArray(); 10 11 ByteArrayInputStream bis = new ByteArrayInputStream(bytes); 12 ObjectInputStream ois = new ObjectInputStream(bis); 13 14 Ship s2 = (Ship) ois.readObject(); 15 16 date.setTime(234243234L); 17 System.out.println(s1); 18 System.out.println(s1.getName() + ":" + s1.getBirthDay()); 19 20 s2.setName("多利的儿子"); 21 System.out.println(s2); 22 System.out.println(s2.getName() + ":" + s2.getBirthDay()); 23 } 24 }
上面的代码使用序列化将s1对象保存在了byte数组中,然后将byte数组的数据反序列化得到对象赋值给s2。改变了s1的时间之后,s2的时间并没有发生变化,说明这是一个深复制的过程。
最后说一下原型模式的意义是什么呢?其实为了解决构造函数非常复杂,构造需要很长时间的时候,我们可以使用原型模式节省大量的时间。如果我们在一个类的构造函数里面设置一个Sleep(10),构造1000次(表示这个构造函数非常复杂)。使用一般的new和我们的原型模式产生新的对象来进行比较的话,会发现原型模式非常快,而一般的new非常的慢。
除此之外,在Spring中,构造函数的方法只有两种,一个是单例,一个是原型模式。在工厂模式中,不是用new生产对象(单例还是需要new的),是用原型模式更快的构建对象。