原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
首先,既然原型模式需要创建当前对象的克隆,那么我们就不得不学习克隆(或者叫 拷贝)的知识了。
拷贝分为 浅拷贝 和 深拷贝
定义一个Car类(浅拷贝)
1 package top.bigking.prototype; 2 3 import java.util.Date; 4 5 /** 6 * @Author ABKing 7 * @Date 2020/2/11 下午2:53 8 * 浅拷贝 9 **/ 10 public class Car implements Cloneable { 11 private String name; 12 private Date date; 13 14 @Override 15 protected Object clone() throws CloneNotSupportedException { 16 return super.clone(); 17 } 18 19 public Car() { 20 } 21 22 public Car(String name, Date date) { 23 this.name = name; 24 this.date = date; 25 } 26 27 public String getName() { 28 return name; 29 } 30 31 public void setName(String name) { 32 this.name = name; 33 } 34 35 public Date getDate() { 36 return date; 37 } 38 39 public void setDate(Date date) { 40 this.date = date; 41 } 42 }
定义一个Car2类(深拷贝)
1 package top.bigking.prototype; 2 3 import java.util.Date; 4 5 /** 6 * @Author ABKing 7 * @Date 2020/2/11 下午5:39 8 * 深拷贝 9 **/ 10 public class Car2 implements Cloneable { 11 private String name; 12 private Date date; 13 14 @Override 15 protected Object clone() throws CloneNotSupportedException { 16 Object obj = super.clone(); 17 Car2 car = (Car2) obj; 18 car.setDate((Date) this.date.clone()); 19 return obj; 20 } 21 22 public Car2() { 23 } 24 25 public Car2(String name, Date date) { 26 this.name = name; 27 this.date = date; 28 } 29 30 public String getName() { 31 return name; 32 } 33 34 public void setName(String name) { 35 this.name = name; 36 } 37 38 public Date getDate() { 39 return date; 40 } 41 42 public void setDate(Date date) { 43 this.date = date; 44 } 45 }
测试:
1 package top.bigking.prototype; 2 3 import org.junit.Test; 4 5 import java.util.Date; 6 7 /** 8 * @Author ABKing 9 * @Date 2020/2/11 下午4:40 10 **/ 11 public class TestPrototype { 12 //浅拷贝 13 @Test 14 public void testShallowCopy() throws CloneNotSupportedException { 15 Date date = new Date(11314211L); 16 Car car1 = new Car("兰博基尼", date); 17 Car car2 = (Car) car1.clone(); //拷贝 18 19 System.out.println("第一辆车:" + car1 + car1.getName() + "-----" + car1.getDate()); 20 System.out.println("第二辆车:" + car2 + car2.getName() + "-----" + car2.getDate()); 21 22 date.setTime(4444524L); 23 24 System.out.println("--------修改date后-------"); 25 26 System.out.println("第一辆车:" + car1 + car1.getName() + "-----" + car1.getDate()); 27 System.out.println("第二辆车:" + car2 + car2.getName() + "-----" + car2.getDate()); 28 } 29 //深拷贝 30 @Test 31 public void testDeepCopy() throws CloneNotSupportedException { 32 Date date = new Date(11314211L); 33 Car2 car1 = new Car2("兰博基尼", date); 34 Car2 car2 = (Car2) car1.clone(); //拷贝 35 36 System.out.println("第一辆车:" + car1 + car1.getName() + "-----" + car1.getDate()); 37 System.out.println("第二辆车:" + car2 + car2.getName() + "-----" + car2.getDate()); 38 39 date.setTime(4444524L); 40 41 System.out.println("--------修改date后-------"); 42 43 System.out.println("第一辆车:" + car1 + car1.getName() + "-----" + car1.getDate()); 44 System.out.println("第二辆车:" + car2 + car2.getName() + "-----" + car2.getDate()); 45 } 46 }
运行结果分别为:
1 /usr/local/java/jdk1.8.0_231/bin/java -ea -Didea.test.cyclic.buffer.size=1048576 -javaagent:/usr/local/bin/idea-IU-193.5662.53/lib/idea_rt.jar=37975:/usr/local/bin/idea-IU-193.5662.53/bin -Dfile.encoding=UTF-8 -classpath /usr/local/bin/idea-IU-193.5662.53/lib/idea_rt.jar:/usr/local/bin/idea-IU-193.5662.53/plugins/junit/lib/junit5-rt.jar:/usr/local/bin/idea-IU-193.5662.53/plugins/junit/lib/junit-rt.jar:/usr/local/java/jdk1.8.0_231/jre/lib/charsets.jar:/usr/local/java/jdk1.8.0_231/jre/lib/deploy.jar:/usr/local/java/jdk1.8.0_231/jre/lib/ext/cldrdata.jar:/usr/local/java/jdk1.8.0_231/jre/lib/ext/dnsns.jar:/usr/local/java/jdk1.8.0_231/jre/lib/ext/jaccess.jar:/usr/local/java/jdk1.8.0_231/jre/lib/ext/jfxrt.jar:/usr/local/java/jdk1.8.0_231/jre/lib/ext/localedata.jar:/usr/local/java/jdk1.8.0_231/jre/lib/ext/nashorn.jar:/usr/local/java/jdk1.8.0_231/jre/lib/ext/sunec.jar:/usr/local/java/jdk1.8.0_231/jre/lib/ext/sunjce_provider.jar:/usr/local/java/jdk1.8.0_231/jre/lib/ext/sunpkcs11.jar:/usr/local/java/jdk1.8.0_231/jre/lib/ext/zipfs.jar:/usr/local/java/jdk1.8.0_231/jre/lib/javaws.jar:/usr/local/java/jdk1.8.0_231/jre/lib/jce.jar:/usr/local/java/jdk1.8.0_231/jre/lib/jfr.jar:/usr/local/java/jdk1.8.0_231/jre/lib/jfxswt.jar:/usr/local/java/jdk1.8.0_231/jre/lib/jsse.jar:/usr/local/java/jdk1.8.0_231/jre/lib/management-agent.jar:/usr/local/java/jdk1.8.0_231/jre/lib/plugin.jar:/usr/local/java/jdk1.8.0_231/jre/lib/resources.jar:/usr/local/java/jdk1.8.0_231/jre/lib/rt.jar:/home/king/IdeaProjects/GOF_23/target/test-classes:/home/king/IdeaProjects/GOF_23/target/classes:/home/king/maven/repository/junit/junit/4.10/junit-4.10.jar:/home/king/maven/repository/org/hamcrest/hamcrest-core/1.1/hamcrest-core-1.1.jar com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit4 top.bigking.prototype.TestPrototype,testShallowCopy 2 第一辆车:top.bigking.prototype.Car@579bb367兰博基尼-----Thu Jan 01 11:08:34 CST 1970 3 第二辆车:top.bigking.prototype.Car@255316f2兰博基尼-----Thu Jan 01 11:08:34 CST 1970 4 --------修改date后------- 5 第一辆车:top.bigking.prototype.Car@579bb367兰博基尼-----Thu Jan 01 09:14:04 CST 1970 6 第二辆车:top.bigking.prototype.Car@255316f2兰博基尼-----Thu Jan 01 09:14:04 CST 1970 7 8 Process finished with exit code 0
可以很明显的看到,修改date后,时间并没有发生改变
运行第二个JUnit测试:
1 /usr/local/java/jdk1.8.0_231/bin/java -ea -Didea.test.cyclic.buffer.size=1048576 -javaagent:/usr/local/bin/idea-IU-193.5662.53/lib/idea_rt.jar=36785:/usr/local/bin/idea-IU-193.5662.53/bin -Dfile.encoding=UTF-8 -classpath /usr/local/bin/idea-IU-193.5662.53/lib/idea_rt.jar:/usr/local/bin/idea-IU-193.5662.53/plugins/junit/lib/junit5-rt.jar:/usr/local/bin/idea-IU-193.5662.53/plugins/junit/lib/junit-rt.jar:/usr/local/java/jdk1.8.0_231/jre/lib/charsets.jar:/usr/local/java/jdk1.8.0_231/jre/lib/deploy.jar:/usr/local/java/jdk1.8.0_231/jre/lib/ext/cldrdata.jar:/usr/local/java/jdk1.8.0_231/jre/lib/ext/dnsns.jar:/usr/local/java/jdk1.8.0_231/jre/lib/ext/jaccess.jar:/usr/local/java/jdk1.8.0_231/jre/lib/ext/jfxrt.jar:/usr/local/java/jdk1.8.0_231/jre/lib/ext/localedata.jar:/usr/local/java/jdk1.8.0_231/jre/lib/ext/nashorn.jar:/usr/local/java/jdk1.8.0_231/jre/lib/ext/sunec.jar:/usr/local/java/jdk1.8.0_231/jre/lib/ext/sunjce_provider.jar:/usr/local/java/jdk1.8.0_231/jre/lib/ext/sunpkcs11.jar:/usr/local/java/jdk1.8.0_231/jre/lib/ext/zipfs.jar:/usr/local/java/jdk1.8.0_231/jre/lib/javaws.jar:/usr/local/java/jdk1.8.0_231/jre/lib/jce.jar:/usr/local/java/jdk1.8.0_231/jre/lib/jfr.jar:/usr/local/java/jdk1.8.0_231/jre/lib/jfxswt.jar:/usr/local/java/jdk1.8.0_231/jre/lib/jsse.jar:/usr/local/java/jdk1.8.0_231/jre/lib/management-agent.jar:/usr/local/java/jdk1.8.0_231/jre/lib/plugin.jar:/usr/local/java/jdk1.8.0_231/jre/lib/resources.jar:/usr/local/java/jdk1.8.0_231/jre/lib/rt.jar:/home/king/IdeaProjects/GOF_23/target/test-classes:/home/king/IdeaProjects/GOF_23/target/classes:/home/king/maven/repository/junit/junit/4.10/junit-4.10.jar:/home/king/maven/repository/org/hamcrest/hamcrest-core/1.1/hamcrest-core-1.1.jar com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit4 top.bigking.prototype.TestPrototype,testDeepCopy 2 第一辆车:top.bigking.prototype.Car2@579bb367兰博基尼-----Thu Jan 01 11:08:34 CST 1970 3 第二辆车:top.bigking.prototype.Car2@255316f2兰博基尼-----Thu Jan 01 11:08:34 CST 1970 4 --------修改date后------- 5 第一辆车:top.bigking.prototype.Car2@579bb367兰博基尼-----Thu Jan 01 09:14:04 CST 1970 6 第二辆车:top.bigking.prototype.Car2@255316f2兰博基尼-----Thu Jan 01 11:08:34 CST 1970 7 8 Process finished with exit code 0
可以看到,时间已经发生了变化。
什么时候会用到拷贝呢?
答案是 当使用new关键字创建对象太耗时时,使用拷贝的方法可以大大加快速度
1 package top.bigking.prototype; 2 3 import org.junit.Test; 4 5 import java.io.ObjectInputStream; 6 7 /** 8 * @Author ABKing 9 * @Date 2020/2/13 下午5:39 10 **/ 11 public class TestPrototype { 12 //测试直接new的方式 13 @Test 14 public void testNew(){ 15 long start = System.currentTimeMillis(); 16 for (int i = 0; i < 1000; i++) { 17 TestCar car = new TestCar(); 18 } 19 long end = System.currentTimeMillis(); 20 System.out.println("使用new的方法总耗时:" + (end - start)); 21 22 } 23 @Test 24 public void testCopy() throws CloneNotSupportedException { 25 long start = System.currentTimeMillis(); 26 TestCar car1 = new TestCar(); 27 for (int i = 0; i < 1000; i++) { 28 TestCar car2 = (TestCar) car1.clone(); 29 } 30 TestCar car2 = (TestCar) car1.clone(); 31 long end = System.currentTimeMillis(); 32 System.out.println("使用clone()的方法总耗时: " + (end - start)); 33 } 34 } 35 class TestCar implements Cloneable{ 36 public TestCar(){ 37 try { 38 Thread.sleep(10); // 模拟创建对象的时间 39 } catch (InterruptedException e) { 40 e.printStackTrace(); 41 } 42 } 43 44 @Override 45 protected Object clone() throws CloneNotSupportedException { 46 return super.clone(); 47 } 48 }
运行第一个JUnit测试,执行结果如下:
使用new的方法总耗时:10121
运行第二个JUnit测试,执行结果如下:
使用clone()的方法总耗时: 11
可以看到,差距非常明显,使用clone()方法,几乎不耗时间
当然,值得注意的是,当new非常耗时的时候,两者差距才很明显,如果把本例中的Thread.sleep(10)注释掉,两者所耗时均为0,几乎不耗时间。
开发中的应用场景:
原型模式一般很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。
spring中bean对象的创建实际就是两种:单例模式和原型模式(当然,原型模式需要和工厂模式搭配起来)。