1、实现机制
- 实现机制:
通过实现 Cloneable接口,再重写 clone()方法返回想要的对象。原型模式实现clone接口的时候必须使用深拷贝。
原型模式实际上就是从一个对象再创建另外一个可定制的对象,且无需知道任何创建的细节。在初始化的信息不发生变化的情况下,克隆是最好的办法,既隐藏了对象创建的细节,又大大提高了性能。因为如果不用 clone,每次 new 都需要执行一次构造函数,如果构造函数的执行时间很长,那么多次的执行初始化操作就太低效了。
- 优点:
- 使用原型模式创建对象比直接new一个对象,在性能上优势明显。因为 Object 类的 clone() 方法是一个本地方法,直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。
- 使用原型模式可以简化对象的创建,使得创建对象很简单。
- 逃避构造函数的约束。使用原型模式复制对象不会调用类的构造方法。因为对象的复制是通过调用Object类的clone方法来完成的,它直接在内存中复制数据,因此不会调用到类的构造方法。不但构造方法中的代码不会执行,甚至连访问权限都对原型模式无效。
- 缺点:
必须实现Cloneable接口。
2、使用场景
希望以一个对象为原型,创建其他近似的对象的时候。
代码优化场景,该场景在业务中用的比较少。一般结合工厂方法模式使用。
多个调用者使用时,并且需要修改对象中的值,可以考虑原型模式来提供对象。
3、使用案例
3.1、案例说明
以下案例,实现场景为,一个试题库中含选择题和简答题,要求根据每个考生将考题顺序打乱,同时将选择题的选项顺序打乱。
3.2、代码结构
3.3、案例实现
3.3.1、实体类
- 简答题实体类 (EssayQuestion)
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 简答题
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class EssayQuestion {
/**
* 题干
*/
private String questionStem;
/**
* 答案
*/
private String answer;
}
- 选择题实体类(ChoiceQuestion)
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.HashMap;
/**
* 选择题
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ChoiceQuestion {
/**
* 题干
*/
private String questionStem;
/**
* 选项
*/
private HashMap<String, String> option;
/**
* 答案
*/
private String key;
}
3.3.2、工具类
- OutOfOrderHelper 类
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.HashMap;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OutOfOrderHelper {
/**
* 选项
*/
private HashMap<String, String> option;
/**
* 答案
*/
private String key;
}
- 选择题选项乱序工具类(OutOfOrderUtil )
import java.util.*;
/**
* 乱序工具类
*/
public class OutOfOrderUtil {
/**
* 乱序Map元素,记录对应答案key
* @param option 选项
* @param key 答案
* @return OutOfOrderHelper 选项乱序后的对象
*/
public static OutOfOrderHelper random(Map<String, String> option, String key) {
// 获取所有选项的key值:(例:A、B、C、D)
Set<String> keySet = option.keySet();
ArrayList<String> keyList = new ArrayList<String>(keySet);
// 使用默认随机源对列表进行置换,所有key置换发生的可能性都是大致相等的。
Collections.shuffle(keyList);
HashMap<String, String> newOption = new HashMap<>();
int idx = 0;
String newKey = "";
for (String next : keySet) {
String randomKey = keyList.get(idx++);
if (key.equals(next)) {
newKey = randomKey;
}
newOption.put(randomKey, option.get(next));
}
return new OutOfOrderHelper(newOption, newKey);
}
}
3.3.3、实现类
- 实现类(ExamHelper),
实现 Cloneable类,重写 clone() 方法
import com.prototype.util.OutOfOrderHelper;
import com.prototype.util.OutOfOrderUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ExamHelper implements Cloneable {
/**
* 考生姓名
*/
private String candidate;
/**
* 考号
*/
private String candidateNumber;
/**
* 简答题集合
*/
private ArrayList<EssayQuestion> essayQuestions = new ArrayList<>();
/**
* 问答题集合
*/
private ArrayList<ChoiceQuestion> choiceQuestions = new ArrayList<>();
public ExamHelper append(EssayQuestion essayQuestion) {
this.essayQuestions.add(essayQuestion);
return this;
}
public ExamHelper append(ChoiceQuestion choiceQuestion) {
this.choiceQuestions.add(choiceQuestion);
return this;
}
/**
* 复制对象
*
* @return
* @throws CloneNotSupportedException
*/
@Override
public Object clone() throws CloneNotSupportedException {
ExamHelper examHelper = (ExamHelper) super.clone();
examHelper.choiceQuestions = (ArrayList<ChoiceQuestion>) this.choiceQuestions.clone();
examHelper.essayQuestions = (ArrayList<EssayQuestion>) this.essayQuestions.clone();
// 对题目进行乱序处理
Collections.shuffle(examHelper.essayQuestions);
Collections.shuffle(examHelper.choiceQuestions);
// 对每个选择题的选项进行乱序处理
ArrayList<ChoiceQuestion> choiceQuestionList = examHelper.choiceQuestions;
choiceQuestionList.forEach(t -> {
OutOfOrderHelper outOfOrderHelper = OutOfOrderUtil.random(t.getOption(), t.getKey());
t.setOption(outOfOrderHelper.getOption());
t.setKey(outOfOrderHelper.getKey());
});
return examHelper;
}
@Override
public String toString() {
StringBuilder detail = new StringBuilder("考生:" + candidate + "\r\n" +
"考号:" + candidateNumber + "\r\n" +
"--------------------------------------------\r\n" +
"一、选择题" + "\r\n\n");
for (int idx = 0; idx < choiceQuestions.size(); idx++) {
detail.append("第").append(idx + 1).append("题:").append(choiceQuestions.get(idx).getQuestionStem()).append("\r\n");
Map<String, String> option = choiceQuestions.get(idx).getOption();
for (String key : option.keySet()) {
detail.append(key).append(":").append(option.get(key)).append("\r\n");
;
}
detail.append("答案:").append(choiceQuestions.get(idx).getKey()).append("\r\n\n");
}
detail.append("二、简答题" + "\r\n\n");
for (int idx = 0; idx < essayQuestions.size(); idx++) {
detail.append("第").append(idx + 1).append("题:").append(essayQuestions.get(idx).getQuestionStem()).append("\r\n");
detail.append("答案:").append(essayQuestions.get(idx).getAnswer()).append("\r\n\n");
}
return detail.toString();
}
}
- ExamMaker 类
import java.util.HashMap;
public class ExamMaker {
ExamHelper examHelper = new ExamHelper();
public ExamMaker() {
HashMap<String, String> option1 = new HashMap<>();
option1.put("A", "继承自Throwable");
option1.put("B", "Serialable");
option1.put("C", "不记得,反正不正确");
option1.put("D", "Run");
HashMap<String, String> option2 = new HashMap<>();
option2.put("A", "class中的constructor不可省略");
option2.put("B", "constructor必须与class同名,但方法不能与class同名");
option2.put("C", "constructor在一个对象被new时执行");
option2.put("D", "一个class只能定义一个constructor");
examHelper.append(new ChoiceQuestion("下面关于java.lang.Exception类的说法正确的是()", option1, "A"))
.append(new ChoiceQuestion("下列说法正确的有()", option2, "C"));
examHelper.append(new EssayQuestion("JDBC操作数据库的步骤?",
"\n" +
"1.加载数据库驱动\n" +
"2.建立到数据库的连接\n" +
"3.定义sql,获取sql执行环境,设置相应的参数\n" +
"4.执行sql,处理sql执行结果——DML语句返回int, DQL语句返回结果集对象 ResultSet\n" +
"5.释放资源"))
.append(new EssayQuestion("String,StringBuilder,StringBuffer的区别",
"\n" +
"1、 String是定长字符串;StringBuilder,StringBuffer是变长字符串\n" +
"2、 StringBuilder是线程非安全,一般用于单线程中,执行效率较StringBuffer高,StringBuffer是线程安全的,如果在多个线程中需要同步则采用此类"))
.append(new EssayQuestion("List和Map的区别",
"\n" +
"List:是存储单列数据的集合,存储的数据有序且可重复\n" +
"Map: 是存储双列数据的集合,采用键值对的形式进行存储,存储的数据是无序的,且key不能重复,但是value值可以重复"));
}
/**
* 创建试卷
* @param candidate
* @param candidateNumber
* @return
* @throws CloneNotSupportedException
*/
public String createExam(String candidate, String candidateNumber) throws CloneNotSupportedException {
ExamHelper examHelperClone = (ExamHelper) examHelper.clone();
examHelperClone.setCandidate(candidate);
examHelperClone.setCandidateNumber(candidateNumber);
return examHelperClone.toString();
}
}
3.3.4、测试类
public class ExamTest {
public static void main(String[] args) {
ExamMaker examMaker = new ExamMaker();
try {
System.out.println(examMaker.createExam("张三", "9527"));
System.out.println(examMaker.createExam("李四", "9528"));
System.out.println(examMaker.createExam("王五", "9529"));
System.out.println(examMaker.createExam("陈六", "9530"));
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
3.3.5、测试结果
考生:张三
考号:9527
--------------------------------------------
一、选择题
第1题:下列说法正确的有()
A:一个class只能定义一个constructor
B:class中的constructor不可省略
C:constructor必须与class同名,但方法不能与class同名
D:constructor在一个对象被new时执行
答案:D
第2题:下面关于java.lang.Exception类的说法正确的是()
A:继承自Throwable
B:Run
C:不记得,反正不正确
D:Serialable
答案:A
二、简答题
第1题:String,StringBuilder,StringBuffer的区别
答案:
1、 String是定长字符串;StringBuilder,StringBuffer是变长字符串
2、 StringBuilder是线程非安全,一般用于单线程中,执行效率较StringBuffer高,StringBuffer是线程安全的,如果在多个线程中需要同步则采用此类
第2题:List和Map的区别
答案:
List:是存储单列数据的集合,存储的数据有序且可重复
Map: 是存储双列数据的集合,采用键值对的形式进行存储,存储的数据是无序的,且key不能重复,但是value值可以重复
第3题:JDBC操作数据库的步骤?
答案:
1.加载数据库驱动
2.建立到数据库的连接
3.定义sql,获取sql执行环境,设置相应的参数
4.执行sql,处理sql执行结果——DML语句返回int, DQL语句返回结果集对象 ResultSet
5.释放资源
考生:李四
考号:9528
--------------------------------------------
一、选择题
第1题:下列说法正确的有()
A:constructor在一个对象被new时执行
B:class中的constructor不可省略
C:constructor必须与class同名,但方法不能与class同名
D:一个class只能定义一个constructor
答案:A
第2题:下面关于java.lang.Exception类的说法正确的是()
A:Run
B:继承自Throwable
C:Serialable
D:不记得,反正不正确
答案:B
二、简答题
第1题:List和Map的区别
答案:
List:是存储单列数据的集合,存储的数据有序且可重复
Map: 是存储双列数据的集合,采用键值对的形式进行存储,存储的数据是无序的,且key不能重复,但是value值可以重复
第2题:JDBC操作数据库的步骤?
答案:
1.加载数据库驱动
2.建立到数据库的连接
3.定义sql,获取sql执行环境,设置相应的参数
4.执行sql,处理sql执行结果——DML语句返回int, DQL语句返回结果集对象 ResultSet
5.释放资源
第3题:String,StringBuilder,StringBuffer的区别
答案:
1、 String是定长字符串;StringBuilder,StringBuffer是变长字符串
2、 StringBuilder是线程非安全,一般用于单线程中,执行效率较StringBuffer高,StringBuffer是线程安全的,如果在多个线程中需要同步则采用此类
考生:王五
考号:9529
--------------------------------------------
一、选择题
第1题:下面关于java.lang.Exception类的说法正确的是()
A:Run
B:继承自Throwable
C:Serialable
D:不记得,反正不正确
答案:B
第2题:下列说法正确的有()
A:class中的constructor不可省略
B:constructor必须与class同名,但方法不能与class同名
C:一个class只能定义一个constructor
D:constructor在一个对象被new时执行
答案:D
二、简答题
第1题:JDBC操作数据库的步骤?
答案:
1.加载数据库驱动
2.建立到数据库的连接
3.定义sql,获取sql执行环境,设置相应的参数
4.执行sql,处理sql执行结果——DML语句返回int, DQL语句返回结果集对象 ResultSet
5.释放资源
第2题:String,StringBuilder,StringBuffer的区别
答案:
1、 String是定长字符串;StringBuilder,StringBuffer是变长字符串
2、 StringBuilder是线程非安全,一般用于单线程中,执行效率较StringBuffer高,StringBuffer是线程安全的,如果在多个线程中需要同步则采用此类
第3题:List和Map的区别
答案:
List:是存储单列数据的集合,存储的数据有序且可重复
Map: 是存储双列数据的集合,采用键值对的形式进行存储,存储的数据是无序的,且key不能重复,但是value值可以重复
考生:陈六
考号:9530
--------------------------------------------
一、选择题
第1题:下面关于java.lang.Exception类的说法正确的是()
A:继承自Throwable
B:Run
C:Serialable
D:不记得,反正不正确
答案:A
第2题:下列说法正确的有()
A:class中的constructor不可省略
B:constructor必须与class同名,但方法不能与class同名
C:constructor在一个对象被new时执行
D:一个class只能定义一个constructor
答案:C
二、简答题
第1题:JDBC操作数据库的步骤?
答案:
1.加载数据库驱动
2.建立到数据库的连接
3.定义sql,获取sql执行环境,设置相应的参数
4.执行sql,处理sql执行结果——DML语句返回int, DQL语句返回结果集对象 ResultSet
5.释放资源
第2题:String,StringBuilder,StringBuffer的区别
答案:
1、 String是定长字符串;StringBuilder,StringBuffer是变长字符串
2、 StringBuilder是线程非安全,一般用于单线程中,执行效率较StringBuffer高,StringBuffer是线程安全的,如果在多个线程中需要同步则采用此类
第3题:List和Map的区别
答案:
List:是存储单列数据的集合,存储的数据有序且可重复
Map: 是存储双列数据的集合,采用键值对的形式进行存储,存储的数据是无序的,且key不能重复,但是value值可以重复
.