原型模式主要解决的问题就是创建重复对象,而这部分对象内容本身⽐较复杂,生成过程可能从库或者RPC接⼝(远程接口调用)中获取数据的耗时较长,因此采用克隆的方式节省时间,clone方法最终将调用JVM中的原生方法完成复制也就是调用底层的c++代码.
根据自己的开发业务经验感觉此模式主要适用于对象大部分属性相同,但部分属性不同的场景.比如说考试的试卷,每个人的考题是相同的,但是姓名和学号是不同的.就这一场景进行模拟一下 .要求:小明 小红输入姓名以及学号之后就能看到自己的考题内容.
考题单元内容:
public class TestQuestionsUnit {
// 题目
private String question;
// 题目选项内容
private Map<String,String> questionMap;
// 答案
private String answer;
// 省略get/set..
@Override
public String toString() {
return "TestQuestions{" +
"question='" + question + '\'' +
", questionMap=" + questionMap +
", answer='" + answer + '\'' +
'}';
}
}
试卷:
public class TestQuestions {
// 姓名
private String name;
// 考试编号
private String no;
// 考试题目集合
private List<TestQuestionsUnit> testQuestionsUnitList;
// 省略get/set..
@Override
public String toString() {
return "TestQuestions{" +
"name='" + this.name + '\'' +
", no='" + this.no + '\'' +
", testQuestionsUnitList='" + testQuestionsUnitList + '\'' +
'}';
}
}
分发考卷1.0:
public static void main(String[] args) {
// 给小明创建考卷
// 构造题库
ArrayList<TestQuestionsUnit> testQuestionsUnits = new ArrayList<>();
// 第一题
TestQuestionsUnit testQuestionsUnit = new TestQuestionsUnit();
testQuestionsUnit.setQuestion("水的沸点是多少度( )");
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("A","97摄氏度");
hashMap.put("B","98摄氏度");
hashMap.put("C","99摄氏度");
hashMap.put("D","100摄氏度");
testQuestionsUnit.setQuestionMap(hashMap);
testQuestionsUnits.add(testQuestionsUnit);
// 第二题
TestQuestionsUnit testQuestionsUnit2 = new TestQuestionsUnit();
testQuestionsUnit2.setQuestion("一天有多少个小时( )");
HashMap<String, String> hashMap2 = new HashMap<>();
hashMap2.put("A","48");
hashMap2.put("B","36");
hashMap2.put("C","24");
hashMap2.put("D","12");
testQuestionsUnit2.setQuestionMap(hashMap2);
testQuestionsUnits.add(testQuestionsUnit2);
// 第三题
TestQuestionsUnit testQuestionsUnit3 = new TestQuestionsUnit();
testQuestionsUnit3.setQuestion("先有鸡还是先有蛋?");
testQuestionsUnits.add(testQuestionsUnit3);
TestQuestions testQuestions = new TestQuestions();
testQuestions.setName("小明");
testQuestions.setNo("0001");
testQuestions.setTestQuestionsUnitList(testQuestionsUnits);
System.out.println("小明试卷:"+testQuestions);
// 给小红创建考卷
// 构造题库
ArrayList<TestQuestionsUnit> testQuestionsUnits2 = new ArrayList<>();
// 第一题
TestQuestionsUnit testQuestionsUnit21 = new TestQuestionsUnit();
testQuestionsUnit21.setQuestion("水的沸点是多少度( )");
HashMap<String, String> hashMap21 = new HashMap<>();
hashMap21.put("A","97摄氏度");
hashMap21.put("B","98摄氏度");
hashMap21.put("C","99摄氏度");
hashMap21.put("D","100摄氏度");
testQuestionsUnit21.setQuestionMap(hashMap21);
testQuestionsUnits2.add(testQuestionsUnit21);
// 第二题
TestQuestionsUnit testQuestionsUnit22 = new TestQuestionsUnit();
testQuestionsUnit22.setQuestion("一天有多少个小时( )");
HashMap<String, String> hashMap22 = new HashMap<>();
hashMap22.put("A","48");
hashMap22.put("B","36");
hashMap22.put("C","24");
hashMap22.put("D","12");
testQuestionsUnit2.setQuestionMap(hashMap22);
testQuestionsUnits2.add(testQuestionsUnit2);
// 第三题
TestQuestionsUnit testQuestionsUnit23 = new TestQuestionsUnit();
testQuestionsUnit23.setQuestion("先有鸡还是先有蛋?");
testQuestionsUnits2.add(testQuestionsUnit23);
TestQuestions testQuestions2 = new TestQuestions();
testQuestions2.setName("小红");
testQuestions2.setNo("0002");
testQuestions2.setTestQuestionsUnitList(testQuestionsUnits2);
System.out.println("小红试卷:"+testQuestions2);
}
分发考卷1.0输出内容:
小明试卷:TestQuestions{
name='小明', no='0001', testQuestionsUnitList='[TestQuestions{question='水的沸点是多少度( )', questionMap={A=97摄氏度, B=98摄氏度, C=99摄氏度, D=100摄氏度}, answer='null'}, TestQuestions{question='一天有多少个小时( )', questionMap={A=48, B=36, C=24, D=12}, answer='null'}, TestQuestions{question='先有鸡还是先有蛋?', questionMap=null, answer='null'}]'}
小红试卷:TestQuestions{
name='小红', no='0002', testQuestionsUnitList='[TestQuestions{question='水的沸点是多少度( )', questionMap={A=97摄氏度, B=98摄氏度, C=99摄氏度, D=100摄氏度}, answer='null'}, TestQuestions{question='一天有多少个小时( )', questionMap={A=48, B=36, C=24, D=12}, answer='null'}, TestQuestions{question='先有鸡还是先有蛋?', questionMap=null, answer='null'}]'}
发现试题内容都是相同的,所以考虑将试题内容在创建考卷的初始化中将试题创建完成,这里采用静态代码块的方式在考卷构造方法之前将考题内容加载到考卷中,所以有了考卷2.0:
public class TestQuestions {
// 初始化考题内容
static {
// 构造方法之前初始化题库
ArrayList<TestQuestionsUnit> testQuestionsUnits = new ArrayList<>();
// 第一题
TestQuestionsUnit testQuestionsUnit = new TestQuestionsUnit();
testQuestionsUnit.setQuestion("水的沸点是多少度( )");
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("A","97摄氏度");
hashMap.put("B","98摄氏度");
hashMap.put("C","99摄氏度");
hashMap.put("D","100摄氏度");
testQuestionsUnit.setQuestionMap(hashMap);
testQuestionsUnits.add(testQuestionsUnit);
// 第二题
TestQuestionsUnit testQuestionsUnit2 = new TestQuestionsUnit();
testQuestionsUnit2.setQuestion("一天有多少个小时( )");
HashMap<String, String> hashMap2 = new HashMap<>();
hashMap.put("A","48");
hashMap.put("B","36");
hashMap.put("C","24");
hashMap.put("D","12");
testQuestionsUnit2.setQuestionMap(hashMap);
testQuestionsUnits.add(testQuestionsUnit2);
// 第三题
TestQuestionsUnit testQuestionsUnit3 = new TestQuestionsUnit();
testQuestionsUnit3.setQuestion("先有鸡还是先有蛋?");
testQuestionsUnits.add(testQuestionsUnit3);
TestQuestions.testQuestionsUnitList=testQuestionsUnits;
}
// 姓名
private String name;
// 考试编号
private String no;
// 考试题目集合,此处应为静态变量,否则无法在静态代码块中调用
private static List<TestQuestionsUnit> testQuestionsUnitList;
// 无参构造
public TestQuestions() {
}
// 全参构造(两个参数)
public TestQuestions(String name, String no) {
this.name = name;
this.no = no;
new TestQuestions(name,no,testQuestionsUnitList); // 调用三个参数的构造方法,或是使用下面的调用三个参数的构造方法(必须保证在构造方法的第一行,否则idea会报错)
//this(name,no,testQuestionsUnitList);
}
// 全参构造(三个参数)
public TestQuestions(String name, String no,List<TestQuestionsUnit> testQuestionsUnitList) {
this.name = name;
this.no = no;
}
// 省略get/set....
@Override
public String toString() {
return "TestQuestions{" +
"name='" + this.name + '\'' +
", no='" + this.no + '\'' +
", testQuestionsUnitList='" + testQuestionsUnitList + '\'' +
'}';
}
}
分发考卷2.0:
public static void main(String[] args) {
TestQuestions te = new TestQuestions("小明", "001");
TestQuestions te1 = new TestQuestions("小红", "002");
System.out.println(te);
System.out.println(te1);
}
输出结果:
小明试卷:TestQuestions{
name='小明', no='0001', testQuestionsUnitList='[TestQuestions{question='水的沸点是多少度( )', questionMap={A=97摄氏度, B=98摄氏度, C=99摄氏度, D=100摄氏度}, answer='null'}, TestQuestions{question='一天有多少个小时( )', questionMap={A=48, B=36, C=24, D=12}, answer='null'}, TestQuestions{question='先有鸡还是先有蛋?', questionMap=null, answer='null'}]'}
小红试卷:TestQuestions{
name='小红', no='0002', testQuestionsUnitList='[TestQuestions{question='水的沸点是多少度( )', questionMap={A=97摄氏度, B=98摄氏度, C=99摄氏度, D=100摄氏度}, answer='null'}, TestQuestions{question='一天有多少个小时( )', questionMap={A=48, B=36, C=24, D=12}, answer='null'}, TestQuestions{question='先有鸡还是先有蛋?', questionMap=null, answer='null'}]'}
是不是感觉清爽多了,但是使用new的方式创建大量对象(属性大部分相同)耗时是非常多的,原型模式解决的创建相同对象的耗时问题.原因是原型模式中调用clone方法,clone比new优越性在于:new对象需要执行构造方法进行初始化操作,而clone方法不需要调用构造方法,执行效率上clone要更高;
考卷3.0(原型模式):
public class TestQuestionsClone implements Cloneable {
static {
System.out.println("静态代码块中逻辑执行");
// 构造方法之前初始化题库
ArrayList<TestQuestionsUnit> testQuestionsUnits = new ArrayList<>();
// 第一题
TestQuestionsUnit testQuestionsUnit = new TestQuestionsUnit();
testQuestionsUnit.setQuestion("水的沸点是多少度( )");
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("A","97摄氏度");
hashMap.put("B","98摄氏度");
hashMap.put("C","99摄氏度");
hashMap.put("D","100摄氏度");
testQuestionsUnit.setQuestionMap(hashMap);
testQuestionsUnits.add(testQuestionsUnit);
// 第二题
TestQuestionsUnit testQuestionsUnit2 = new TestQuestionsUnit();
testQuestionsUnit2.setQuestion("一天有多少个小时( )");
HashMap<String, String> hashMap2 = new HashMap<>();
hashMap.put("A","48");
hashMap.put("B","36");
hashMap.put("C","24");
hashMap.put("D","12");
testQuestionsUnit2.setQuestionMap(hashMap);
testQuestionsUnits.add(testQuestionsUnit2);
// 第三题
TestQuestionsUnit testQuestionsUnit3 = new TestQuestionsUnit();
testQuestionsUnit3.setQuestion("先有鸡还是先有蛋?");
testQuestionsUnits.add(testQuestionsUnit3);
System.out.println("静态代码块中执行题库初始化:"+testQuestionsUnits);
TestQuestionsClone.testQuestionsUnitList=testQuestionsUnits;
System.out.println("静态代码块中给题库进行赋值结束");
}
// 姓名
private String name;
// 考试编号
private String no;
// 考试题目集合
private static List<TestQuestionsUnit> testQuestionsUnitList;
public TestQuestionsClone(String name, String no) {
this.name = name;
this.no = no;
new TestQuestions(name,no,testQuestionsUnitList); // 调用三个参数的构造方法,或是使用下面的调用三个参数的构造方法(必须保证在第一行,否则需要报错)
//this(name,no,testQuestionsUnitList);
}
public TestQuestionsClone(String name, String no,List<TestQuestionsUnit> testQuestionsUnitList) {
this.name = name;
this.no = no;
}
// 省略get/set方法
@Override
public String toString() {
return "TestQuestions{" +
"name='" + this.name + '\'' +
", no='" + this.no + '\'' +
", testQuestionsUnitList='" + testQuestionsUnitList + '\'' +
'}';
}
// 继承cloneable接口,重写clone方法
@Override
public TestQuestionsClone clone() throws CloneNotSupportedException {
return (TestQuestionsClone)super.clone();
}
}
分发考卷3.0:
public static void main(String[] args) throws CloneNotSupportedException {
TestQuestionsClone testQuestionsClone = new TestQuestionsClone("小明", "001");
System.out.println(testQuestionsClone);
TestQuestionsClone testQuestionsClone2 = testQuestionsClone.clone();
testQuestionsClone2.setName("小红");
testQuestionsClone2.setNo("002");
System.out.println(testQuestionsClone2);
}
输出结果:
小明试卷:TestQuestions{
name='小明', no='0001', testQuestionsUnitList='[TestQuestions{question='水的沸点是多少度( )', questionMap={A=97摄氏度, B=98摄氏度, C=99摄氏度, D=100摄氏度}, answer='null'}, TestQuestions{question='一天有多少个小时( )', questionMap={A=48, B=36, C=24, D=12}, answer='null'}, TestQuestions{question='先有鸡还是先有蛋?', questionMap=null, answer='null'}]'}
小红试卷:TestQuestions{
name='小红', no='0002', testQuestionsUnitList='[TestQuestions{question='水的沸点是多少度( )', questionMap={A=97摄氏度, B=98摄氏度, C=99摄氏度, D=100摄氏度}, answer='null'}, TestQuestions{question='一天有多少个小时( )', questionMap={A=48, B=36, C=24, D=12}, answer='null'}, TestQuestions{question='先有鸡还是先有蛋?', questionMap=null, answer='null'}]'}
实际上就是在2.0的基础上让试卷对象继承cloneable接口重写clone方法.第一次使用new的方式进行创建,之后的每一次都使用克隆的方法进行创建;
下面测试new对象与clone对象的效率:
public static void main(String[] args) throws CloneNotSupportedException {
// 原型模式clone方法
long start = System.currentTimeMillis();
TestQuestionsClone testQuestionsClone = new TestQuestionsClone("小明0", "1");
for (int i = 0; i < 1000; i++) {
TestQuestionsClone testQuestionsClone2 = testQuestionsClone.clone();
testQuestionsClone2.setName("小明"+String.valueOf(++i));
}
long end = System.currentTimeMillis();
System.out.println(end-start); // 耗时:2
// new的方式创建对象
long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
TestQuestions te = new TestQuestions("小明", String.valueOf(i));
}
long end = System.currentTimeMillis();
System.out.println(end-start); // 耗时:19
}
说到clone简单说一下深克隆与浅克隆
浅克隆:创建新的对象,地址值改变,复制对象的基本类型值,如果对象中引用类型,引用还是指向原来的引用对象;考卷3.0中实际上是属于浅克隆,可以打印一下TestQuestionsClone 中的引用testQuestionsUnitList的地址值,克隆出来的地址值与首次创建的TestQuestionsClone 中的引用testQuestionsUnitList的地址值相同;
深克隆:创建新的对象,地址值改变,复制对象的基本类型,如果对象有引用类型,则创建新的引用对象.深克隆与浅克隆的区别在于对象中存在引用的情况,实现深克隆可以将引用中的对象实现cloneable重写clone方法或是实现Serializable接口.