Java设计模式之创建型模式(二)原型模式

原型模式

    • 1、原型模式
      • 1-1、应用场景
      • 1-2、举个 软栗子
      • 1-3、举个 硬栗子
      • 1-4、举个实务栗子
      • 1-5、代码重构

学习原型模式的目的:原型模式的目的在于通过复制现有的实例来创建新的对象,以避免通过构造函数创建对象时可能带来的性能开销,同时可以控制对象的深度复制。
通俗点讲就是原型模式主要解决的问题就是创建⼤量重复的类,⽽我们模拟的场景就需要给不同的⽤户都创建相同的试卷,但这些试卷的题⽬不便于每次都从库中获取,甚⾄有时候需要从远程的RPC中获取。这样都是⾮常耗时的,⽽且随着创建对象的增多将严重影响效率

1、原型模式

定义:
在Java中,原型模式(Prototype Pattern)是一种创建型设计模式,其主要目的是通过使用已有对象作为原型,克隆并生成新的对象,而无需显式地使用构造函数来创建对象。原型模式的核心思想是通过克隆(Clone)现有的对象来创建新的对象,这种克隆可以是浅克隆(Shallow Clone)或深克隆(Deep Clone)。

  • 浅克隆:通过调用对象的clone()方法复制对象,得到一个新的对象实例,新对象与原对象共享部分数据。对于引用类型的成员变量,浅克隆只复制其引用而不创建新的对象。
  • 深克隆:通过自定义克隆逻辑,递归地复制对象及其引用类型的成员变量,得到一个完全独立的新对象实例。深克隆会逐层复制对象及其引用的对象,保证新对象与原对象的数据完全独立。

要实现原型模式,需要满足以下条件:

  1. 被克隆的类必须实现Cloneable接口,这个接口是一个标记接口,表明该类可以被克隆。
  2. 在被克隆的类中覆写clone()方法,实现对象的克隆操作。
  3. 在使用原型模式时,通过调用对象的clone()方法创建新的对象。

通过使用原型模式,可以避免直接使用构造函数复杂地创建对象,提高对象的创建效率和灵活性。
需要注意的是,在克隆过程中如果涉及到引用类型的成员变量,如果要实现深克隆,需要对引用类型的成员变量也进行克隆操作,确保数据的独立性和完整性。
总的来说,原型模式通过克隆现有对象来创建新的对象,实现了对象的复制和创建的分离,提高了对象创建的效率和灵活性。

1-1、应用场景

Java中的原型模式适用于以下业务应用场景:

  1. 对象的创建成本高:当需要创建一个对象的成本很高,比如需要初始化大量的数据、进行复杂的计算或者远程调用等,可以使用原型模式。通过复制已有的对象,避免重复的创建过程,提高性能和效率
  2. 对象的创建过程复杂:有些对象的创建过程比较复杂,涉及到多个步骤或者依赖其他对象。在这种情况下,可以使用原型模式将一个已经初始化好的对象作为原型,通过复制来创建新对象,避免创建过程的复杂性和依赖关系的传递
  3. 动态配置对象:当需要根据用户的不同配置动态生成对象时,可以使用原型模式。通过定义一个原型对象,根据不同的配置参数进行复制,快速生成多个符合用户需求的对象
  4. 多线程环境下的对象创建:在多线程环境下,对象的创建可能会引起线程安全的问题。使用原型模式,可以避免多个线程同时创建对象,从而保证对象的安全性和一致性。

总的来说,原型模式适用于那些创建成本高、创建过程复杂、动态配置或需要在多线程环境下创建对象的场景。通过原型模式,可以通过复制现有的对象来创建新对象,提高性能、简化对象的创建过程,并保证对象的安全性和一致性。

1-2、举个 软栗子

代码如下


写个Person实现Cloneable接口:

package com.rensun.designmode.prototype_mode.shallowcopy2;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;@Slf4j
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person implements Cloneable {private int id;private String name;private Address address;@Overridepublic Person clone() throws CloneNotSupportedException {return (Person) super.clone();}
}

写个Address实现Cloneable接口:

package com.rensun.designmode.prototype_mode.shallowcopy2;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@AllArgsConstructor
@NoArgsConstructor
class Address implements Cloneable {private String city;private String street;@Overridepublic Address clone() throws CloneNotSupportedException {return (Address) super.clone();}
}

写个测试方法:

package com.rensun.designmode.prototype_mode.shallowcopy2;import lombok.extern.slf4j.Slf4j;/***  使用场景:*  软拷贝适用于对象的字段较简单,不包含引用类型字段*  的情况,而且希望拷贝后的对象能够及时地反映原对象*  字段的变化。例如,当对象只包含基本数据类型的字段,*  且需要对对象进行快速的拷贝操作,可以使用软拷贝。**/
@Slf4j
public class TestShallowCopy {public static void main(String[] args) {Address address = new Address("濮阳市", "挥公大道003号");Person person = new Person(1, "岳云鹏", address);try {Person copyPerson = person.clone();log.info("person: {}", person);log.info("shallow copy person: {}", copyPerson);//拷贝后的对象重新赋值copyPerson.setId(2);copyPerson.setName("小驴程序源");copyPerson.getAddress().setCity("北京市");copyPerson.getAddress().setStreet("长安街110号");log.info("------------------拷贝后的对象重新赋值如下:--------------------");log.info("person: {}", person);log.info("shallow copy person: {}", copyPerson);} catch (CloneNotSupportedException e) {log.error("Clone not supported", e);}}
}

我们看一下console输出

已连接到地址为 ''127.0.0.1:52221',传输: '套接字'' 的目标虚拟机
17:15:14.526 [main] INFO com.rensun.designmode.prototype_mode.shallowcopy2.TestShallowCopy - person: Person(id=1, name=岳云鹏, address=Address(city=濮阳市, street=挥公大道003号))
17:15:14.530 [main] INFO com.rensun.designmode.prototype_mode.shallowcopy2.TestShallowCopy - shallow copy person: Person(id=1, name=岳云鹏, address=Address(city=濮阳市, street=挥公大道003号))
17:15:14.530 [main] INFO com.rensun.designmode.prototype_mode.shallowcopy2.TestShallowCopy - ------------------拷贝后的对象重新赋值如下:--------------------
17:15:14.530 [main] INFO com.rensun.designmode.prototype_mode.shallowcopy2.TestShallowCopy - person: Person(id=1, name=岳云鹏, address=Address(city=北京市, street=长安街110号))
17:15:14.530 [main] INFO com.rensun.designmode.prototype_mode.shallowcopy2.TestShallowCopy - shallow copy person: Person(id=2, name=小驴程序源, address=Address(city=北京市, street=长安街110号))
已与地址为 ''127.0.0.1:52221',传输: '套接字'' 的目标虚拟机断开连接进程已结束,退出代码为 0

根据输出结果,可以看到以下信息:

原对象person的信息为:Person(id=1, name=岳云鹏, address=Address(city=濮阳市, street=挥公大道003号))
浅拷贝对象copyPerson的信息为:Person(id=1, name=岳云鹏, address=Address(city=濮阳市, street=挥公大道003号))
拷贝后的对象重新赋值后,原对象person的信息为:Person(id=1, name=岳云鹏, address=Address(city=北京市, street=长安街110号))
拷贝后的对象重新赋值后,浅拷贝对象copyPerson的信息为:Person(id=2, name=小驴程序源, address=Address(city=北京市, street=长安街110号))

从输出结果来看,当对拷贝后的对象copyPerson进行了修改时,原对象person的数据并没有发生变化。这是因为浅拷贝只复制了对象的引用,并没有对引用的对象进行深拷贝。因此,当拷贝对象的引用类型成员变量进行修改时,原对象的成员变量也会相应改变


1-3、举个 硬栗子

在这里插入图片描述

首先Address还是原来的它自己,Person要改写一下clone方法

package com.rensun.designmode.prototype_mode.deepcopy2;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;@Slf4j
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person implements Cloneable {private int id;private String name;private Address address;@Overridepublic Person clone() throws CloneNotSupportedException {Person cPerson = (Person) super.clone();cPerson.address = this.address.clone();return cPerson;}
}

测试类还是原来的测试类,改了个名

package com.rensun.designmode.prototype_mode.deepcopy2;import lombok.extern.slf4j.Slf4j;/***  使用场景:*  硬拷贝适用于对象的字段较复杂,包含引用类型字段的情*  况,希望拷贝后的对象能够独立地管理自己的字段,不受*  原对象影响。例如,当对象包含引用类型的字段,且需要*  对对象进行深度拷贝,确保拷贝后的对象与原对象完全独*  立,可以使用硬拷贝。**/
@Slf4j
public class TestDeepCopy {public static void main(String[] args) {Address address = new Address("濮阳市", "挥公大道003号");Person person = new Person(1, "岳云鹏", address);try {Person copyPerson = person.clone();log.info("person: {}", person);log.info("shallow copy person: {}", copyPerson);//拷贝后的对象重新赋值copyPerson.setId(2);copyPerson.setName("小驴程序源");copyPerson.getAddress().setCity("北京市");copyPerson.getAddress().setStreet("长安街110号");log.info("------------------拷贝后的对象重新赋值如下:--------------------");log.info("person: {}", person);log.info("shallow copy person: {}", copyPerson);} catch (CloneNotSupportedException e) {log.error("Clone not supported", e);}}
}

但是输出结果就有了新变化:

在clone()方法中,首先调用了父类的clone()方法创建了一个新的Person对象,并进行类型转换。接着,通过调用this.address.clone()方法对地址对象进行深拷贝,将拷贝后的地址对象赋值给新创建的Person对象的地址成员变量。这样就实现了深拷贝,拷贝后的对象和原对象拥有了各自的地址对象,互不影响。

总的来说,这段代码示例展示了如何通过在clone()方法中对引用类型成员变量进行深拷贝操作,实现了深拷贝的Person类。通过这种方式,即使修改拷贝对象的引用类型成员变量,原对象的成员变量也不会受到影响

已连接到地址为 ''127.0.0.1:53498',传输: '套接字'' 的目标虚拟机
18:04:04.663 [main] INFO com.rensun.designmode.prototype_mode.deepcopy2.TestDeepCopy - person: Person(id=1, name=岳云鹏, address=Address(city=濮阳市, street=挥公大道003号))
18:04:04.669 [main] INFO com.rensun.designmode.prototype_mode.deepcopy2.TestDeepCopy - shallow copy person: Person(id=1, name=岳云鹏, address=Address(city=濮阳市, street=挥公大道003号))
18:04:04.669 [main] INFO com.rensun.designmode.prototype_mode.deepcopy2.TestDeepCopy - ------------------拷贝后的对象重新赋值如下:--------------------
18:04:04.669 [main] INFO com.rensun.designmode.prototype_mode.deepcopy2.TestDeepCopy - person: Person(id=1, name=岳云鹏, address=Address(city=濮阳市, street=挥公大道003号))
18:04:04.669 [main] INFO com.rensun.designmode.prototype_mode.deepcopy2.TestDeepCopy - shallow copy person: Person(id=2, name=小驴程序源, address=Address(city=北京市, street=长安街110号))
已与地址为 ''127.0.0.1:53498',传输: '套接字'' 的目标虚拟机断开连接

1-4、举个实务栗子

package com.tansun.goods.controller;import cn.hutool.json.JSONUtil;
import com.tansun.goods.prototype_mode.AnswerQuestion;
import com.tansun.goods.prototype_mode.ChoiceQuestion;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** @author Administrator*/
@Slf4j
@RestController
@RequestMapping
public class TestController {public String createPaper(String candidate, String number) {//选择题List<ChoiceQuestion> choiceQuestionList = new ArrayList<ChoiceQuestion>();//问答题List<AnswerQuestion> answerQuestionList = new ArrayList<AnswerQuestion>();Map<String, String> map01 = new HashMap<String, String>();map01.put("A", "JAVA2 EE");map01.put("B", "JAVA2 Card");map01.put("C", "JAVA2 ME");map01.put("D", "JAVA2 HE");map01.put("E", "JAVA2 SE");Map<String, String> map02 = new HashMap<String, String>();map02.put("A", "JAVA程序的main⽅法必须写在类⾥⾯");map02.put("B", "JAVA程序中可以有多个main⽅法");map02.put("C", "JAVA程序中类名必须与⽂件名⼀样");map02.put("D", "JAVA程序的main⽅法中如果只有⼀条语句,可以不⽤{}(⼤括号)括起来");Map<String, String> map03 = new HashMap<String, String>();map03.put("A", "变量由字⺟、下划线、数字、$符号随意组成;");map03.put("B", "变量不能以数字作为开头;");map03.put("C", "A和a在java中是同⼀个变量;");map03.put("D", "不同类型的变量,可以起相同的名字;");Map<String, String> map04 = new HashMap<String, String>();map04.put("A", "STRING");map04.put("B", "x3x;");map04.put("C", "void");map04.put("D", "de$f");Map<String, String> map05 = new HashMap<String, String>();map05.put("A", "31");map05.put("B", "0");map05.put("C", "1");map05.put("D", "2");choiceQuestionList.add(new ChoiceQuestion("JAVA所定义的版本中不包括", map01, "D"));choiceQuestionList.add(new ChoiceQuestion("下列说法正确的是", map02, "A"));choiceQuestionList.add(new ChoiceQuestion("变量命名规范说法正确的是", map03, "B"));choiceQuestionList.add(new ChoiceQuestion("以下()不是合法的标识符", map04, "C"));choiceQuestionList.add(new ChoiceQuestion("表达式(11+3*8)/4%3的值 是", map05, " D"));answerQuestionList.add(new AnswerQuestion("⼩红⻢和⼩⿊⻢⽣的⼩⻢⼏条腿", "4条腿"));answerQuestionList.add(new AnswerQuestion("铁棒打头疼还是⽊棒打头疼", "头最疼"));answerQuestionList.add(new AnswerQuestion("什么床不能睡觉", "⽛床"));answerQuestionList.add(new AnswerQuestion("为什么好⻢不吃回头草", "后⾯的草没了"));//打印当前考生的试卷StringBuilder detail = new StringBuilder("考⽣:" + candidate + "\r\n" + "考号:" + number + "\r\n" + "--------------------------------------------\r\n" + "⼀、选择题" + "\r\n\n");for (int idx = 0; idx < choiceQuestionList.size(); idx++) {detail.append("第").append(idx + 1).append("题:").append(choiceQuestionList.get(idx).getName()).append("\n");Map<String, String> option = choiceQuestionList.get(idx).getOption();option.forEach((k,v)->detail.append(k).append(":").append(option.get(k)).append("\r\n"));detail.append("答 案:").append(choiceQuestionList.get(idx).getKey()).append("\r\n\n");}detail.append("⼆、问答题" + "\r\n\n");for (int idx = 0; idx < answerQuestionList.size(); idx++) {detail.append("第").append(idx + 1).append("题:").append(answerQuestionList.get(idx).getName()).append("\n");detail.append("答 案:").append(answerQuestionList.get(idx).getKey()).append("\r\n\n");}return detail.toString();}
}

选择题类

package com.tansun.goods.prototype_mode;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Map;@Data
@AllArgsConstructor
@NoArgsConstructor
public class ChoiceQuestion {/*** 题⽬*/private String name;/*** 选项A、B、C、D*/private Map<String, String> option;/*** 答案B*/private String key;
}

问答题

package com.tansun.goods.prototype_mode;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@AllArgsConstructor
@NoArgsConstructor
public class AnswerQuestion {/*** 问题*/private String name;/*** 答案*/private String key;
}

运行:

package com.tansun.goods;import com.tansun.goods.controller.TestController;
import org.apache.catalina.core.ApplicationContext;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;@SpringBootTest
class GoodsApplicationTests {@Testpublic void test_QuestionBankController() {TestController questionBankController = new TestController();System.out.println(questionBankController.createPaper("花花", "1000001921032"));System.out.println(questionBankController.createPaper("⾖⾖", "1000001921051"));System.out.println(questionBankController.createPaper("⼤宝", "1000001921987"));}}

结果:

考⽣:花花
考号:1000001921032
--------------------------------------------
⼀、选择题第1题:JAVA所定义的版本中不包括
AJAVA2 EE
BJAVA2 Card
CJAVA2 ME
DJAVA2 HE
EJAVA2 SE
答 案:D2题:下列说法正确的是
AJAVA程序的main⽅法必须写在类⾥⾯
BJAVA程序中可以有多个main⽅法
CJAVA程序中类名必须与⽂件名⼀样
DJAVA程序的main⽅法中如果只有⼀条语句,可以不⽤{}(⼤括号)括起来
答 案:A3题:变量命名规范说法正确的是
A:变量由字⺟、下划线、数字、$符号随意组成;
B:变量不能以数字作为开头;
CA和a在java中是同⼀个变量;
D:不同类型的变量,可以起相同的名字;
答 案:B4题:以下()不是合法的标识符
ASTRING
B:x3x;
Cvoid
D:de$f
答 案:C5题:表达式(11+3*8)/4%3的值 是
A31
B0
C1
D2
答 案: D⼆、问答题第1题:⼩红⻢和⼩⿊⻢⽣的⼩⻢⼏条腿
答 案:4条腿第2题:铁棒打头疼还是⽊棒打头疼
答 案:头最疼第3题:什么床不能睡觉
答 案:⽛床第4题:为什么好⻢不吃回头草
答 案:后⾯的草没了考⽣:⾖⾖
考号:1000001921051
--------------------------------------------
⼀、选择题第1题:JAVA所定义的版本中不包括
AJAVA2 EE
BJAVA2 Card
CJAVA2 ME
DJAVA2 HE
EJAVA2 SE
答 案:D2题:下列说法正确的是
AJAVA程序的main⽅法必须写在类⾥⾯
BJAVA程序中可以有多个main⽅法
CJAVA程序中类名必须与⽂件名⼀样
DJAVA程序的main⽅法中如果只有⼀条语句,可以不⽤{}(⼤括号)括起来
答 案:A3题:变量命名规范说法正确的是
A:变量由字⺟、下划线、数字、$符号随意组成;
B:变量不能以数字作为开头;
CA和a在java中是同⼀个变量;
D:不同类型的变量,可以起相同的名字;
答 案:B4题:以下()不是合法的标识符
ASTRING
B:x3x;
Cvoid
D:de$f
答 案:C5题:表达式(11+3*8)/4%3的值 是
A31
B0
C1
D2
答 案: D⼆、问答题第1题:⼩红⻢和⼩⿊⻢⽣的⼩⻢⼏条腿
答 案:4条腿第2题:铁棒打头疼还是⽊棒打头疼
答 案:头最疼第3题:什么床不能睡觉
答 案:⽛床第4题:为什么好⻢不吃回头草
答 案:后⾯的草没了考⽣:⼤宝
考号:1000001921987
--------------------------------------------
⼀、选择题第1题:JAVA所定义的版本中不包括
AJAVA2 EE
BJAVA2 Card
CJAVA2 ME
DJAVA2 HE
EJAVA2 SE
答 案:D2题:下列说法正确的是
AJAVA程序的main⽅法必须写在类⾥⾯
BJAVA程序中可以有多个main⽅法
CJAVA程序中类名必须与⽂件名⼀样
DJAVA程序的main⽅法中如果只有⼀条语句,可以不⽤{}(⼤括号)括起来
答 案:A3题:变量命名规范说法正确的是
A:变量由字⺟、下划线、数字、$符号随意组成;
B:变量不能以数字作为开头;
CA和a在java中是同⼀个变量;
D:不同类型的变量,可以起相同的名字;
答 案:B4题:以下()不是合法的标识符
ASTRING
B:x3x;
Cvoid
D:de$f
答 案:C5题:表达式(11+3*8)/4%3的值 是
A31
B0
C1
D2
答 案: D⼆、问答题第1题:⼩红⻢和⼩⿊⻢⽣的⼩⻢⼏条腿
答 案:4条腿第2题:铁棒打头疼还是⽊棒打头疼
答 案:头最疼第3题:什么床不能睡觉
答 案:⽛床第4题:为什么好⻢不吃回头草
答 案:后⾯的草没了进程已结束,退出代码0

以上就是三位考试的试卷; 花花、 ⾖、⼤宝,每个⼈的试卷内容是⼀样的这没问题,但是三个⼈的题⽬以及选项顺序都是⼀样,就没有达到我们说希望的乱序要求。⽽且以上这样的代码⾮常难扩展,随着题⽬的不断的增加以及乱序功能的补充,都会让这段代码变
得越来越混乱

1-5、代码重构

针对每⼀个试卷都会使⽤克隆的⽅式进⾏复制,复制完成后将试卷中题⽬以及每个题⽬的答案进⾏乱序处理。这⾥提供了⼯具包;TopicRandomUtil


import java.util.*;public class TopicRandomUtil {/*** 乱序Map元素,记录对应答案key* @param option 题目* @param key    答案* @return Topic 乱序后 {A=c., B=d., C=a., D=b.}*/static public Topic random(Map<String, String> option, String key) {Set<String> keySet = option.keySet();ArrayList<String> keyList = new ArrayList<String>(keySet);Collections.shuffle(keyList);HashMap<String, String> optionNew = new HashMap<String, String>();int idx = 0;String keyNew = "";for (String next : keySet) {String randomKey = keyList.get(idx++);if (key.equals(next)) {keyNew = randomKey;}optionNew.put(randomKey, option.get(next));}return new Topic(optionNew, keyNew);}public Topic random2(Map<String, String> hashMap, String key) {List<String> keys = new ArrayList<>(hashMap.keySet());Collections.shuffle(keys);Map<String, String> shuffledMap = new HashMap<>();String newKey = "";for (int i = 0; i < keys.size(); i++) {String oldKey = keys.get(i);String val = hashMap.get(oldKey);shuffledMap.put(keys.get(i), val);if (oldKey.equals(key)) {newKey = keys.get(i);}}return new Topic(shuffledMap, newKey);}
}

克隆对象


import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;public class QuestionBank implements Cloneable {private String candidate; // 考生private String number;    // 考号private ArrayList<ChoiceQuestion> choiceQuestionList = new ArrayList<ChoiceQuestion>();private ArrayList<AnswerQuestion> answerQuestionList = new ArrayList<AnswerQuestion>();public QuestionBank append(ChoiceQuestion choiceQuestion) {choiceQuestionList.add(choiceQuestion);return this;}public QuestionBank append(AnswerQuestion answerQuestion) {answerQuestionList.add(answerQuestion);return this;}@Overridepublic Object clone() throws CloneNotSupportedException {QuestionBank questionBank = (QuestionBank) super.clone();questionBank.choiceQuestionList = (ArrayList<ChoiceQuestion>) choiceQuestionList.clone();questionBank.answerQuestionList = (ArrayList<AnswerQuestion>) answerQuestionList.clone();// 题目乱序Collections.shuffle(questionBank.choiceQuestionList);Collections.shuffle(questionBank.answerQuestionList);// 答案乱序ArrayList<ChoiceQuestion> choiceQuestionList = questionBank.choiceQuestionList;for (ChoiceQuestion question : choiceQuestionList) {Topic random = TopicRandomUtil.random(question.getOption(), question.getKey());question.setOption(random.getOption());question.setKey(random.getKey());}return questionBank;}public void setCandidate(String candidate) {this.candidate = candidate;}public void setNumber(String number) {this.number = number;}@Overridepublic String toString() {StringBuilder detail = new StringBuilder("考生:" + candidate + "\r\n" + "考号:" + number + "\r\n" + "--------------------------------------------\r\n" + "一、选择题" + "\r\n\n");for (int idx = 0; idx < choiceQuestionList.size(); idx++) {detail.append("第").append(idx + 1).append("题:").append(choiceQuestionList.get(idx).getName()).append("\r\n");Map<String, String> option = choiceQuestionList.get(idx).getOption();for (String key : option.keySet()) {detail.append(key).append(":").append(option.get(key)).append("\r\n");;}detail.append("答案:").append(choiceQuestionList.get(idx).getKey()).append("\r\n\n");}detail.append("二、问答题" + "\r\n\n");for (int idx = 0; idx < answerQuestionList.size(); idx++) {detail.append("第").append(idx + 1).append("题:").append(answerQuestionList.get(idx).getName()).append("\r\n");detail.append("答案:").append(answerQuestionList.get(idx).getKey()).append("\r\n\n");}return detail.toString();}}

这⾥的主要操作内容有三个,分别是;

  • 两个 append() ,对各项题⽬的添加,有点像我们在建造者模式中使⽤的⽅式,添加装修物料

  • clone() ,这⾥的核⼼操作就是对对象的复制,这⾥的复制不只是包括了本身,同时对两个集合也做了复制。只有这样的拷⻉才能确保在操作克隆对象的时候不影响原对象。

  • 乱序操作,在 list 集合中有⼀个⽅法, Collections.shuffle ,可以将原有集合的顺序打乱, 输出⼀个新的顺序。在这⾥我们使⽤此⽅法对题⽬进⾏乱序操作。

初始化试卷数据:


import java.util.HashMap;
import java.util.Map;public class QuestionBankController {private QuestionBank questionBank = new QuestionBank();public QuestionBankController() {Map<String, String> map01 = new HashMap<String, String>();map01.put("A", "JAVA2 EE");map01.put("B", "JAVA2 Card");map01.put("C", "JAVA2 ME");map01.put("D", "JAVA2 HE");map01.put("E", "JAVA2 SE");Map<String, String> map02 = new HashMap<String, String>();map02.put("A", "JAVA程序的main方法必须写在类里面");map02.put("B", "JAVA程序中可以有多个main方法");map02.put("C", "JAVA程序中类名必须与文件名一样");map02.put("D", "JAVA程序的main方法中如果只有一条语句,可以不用{}(大括号)括起来");Map<String, String> map03 = new HashMap<String, String>();map03.put("A", "变量由字母、下划线、数字、$符号随意组成;");map03.put("B", "变量不能以数字作为开头;");map03.put("C", "A和a在java中是同一个变量;");map03.put("D", "不同类型的变量,可以起相同的名字;");Map<String, String> map04 = new HashMap<String, String>();map04.put("A", "STRING");map04.put("B", "x3x;");map04.put("C", "void");map04.put("D", "de$f");Map<String, String> map05 = new HashMap<String, String>();map05.put("A", "31");map05.put("B", "0");map05.put("C", "1");map05.put("D", "2");questionBank.append(new ChoiceQuestion("JAVA所定义的版本中不包括", map01, "D")).append(new ChoiceQuestion("下列说法正确的是", map02, "A")).append(new ChoiceQuestion("变量命名规范说法正确的是", map03, "B")).append(new ChoiceQuestion("以下()不是合法的标识符", map04, "C")).append(new ChoiceQuestion("表达式(11+3*8)/4%3的值是", map05, "D")).append(new AnswerQuestion("小红马和小黑马生的小马几条腿", "4条腿")).append(new AnswerQuestion("铁棒打头疼还是木棒打头疼", "头最疼")).append(new AnswerQuestion("什么床不能睡觉", "牙床")).append(new AnswerQuestion("为什么好马不吃回头草", "后面的草没了"));}public String createPaper(String candidate, String number) throws CloneNotSupportedException {QuestionBank questionBankClone = (QuestionBank) questionBank.clone();questionBankClone.setCandidate(candidate);questionBankClone.setNumber(number);return questionBankClone.toString();}}
  • 这个类的内容就⽐较简单了,主要提供对试卷内容的模式初始化操作(所有考⽣试卷⼀样,题⽬顺序不⼀致)。
  • 以及对外部提供创建试卷的⽅法,在创建的过程中使⽤的是克隆的⽅式; (QuestionBank)questionBank.clone(); ,并最终返回试卷信息。

测试验证:


import org.itstack.demo.design.QuestionBankController;
import org.junit.Test;public class ApiTest {@Testpublic void test_QuestionBank() throws CloneNotSupportedException {QuestionBankController questionBankController = new QuestionBankController();System.out.println(questionBankController.createPaper("郭德纲", "1000001921032"));System.out.println(questionBankController.createPaper("岳云鹏", "1000001921051"));System.out.println(questionBankController.createPaper("于谦", "1000001921987"));}
}

结果

考生:郭德纲
考号:1000001921032
--------------------------------------------
一、选择题第1题:JAVA所定义的版本中不包括
AJAVA2 SE
BJAVA2 HE
CJAVA2 EE
DJAVA2 ME
EJAVA2 Card
答案:B2题:表达式(11+3*8)/4%3的值是
A1
B0
C2
D31
答案:C3题:以下()不是合法的标识符
A:x3x;
Bvoid
CSTRING
D:de$f
答案:B4题:变量命名规范说法正确的是
A:变量由字母、下划线、数字、$符号随意组成;
B:不同类型的变量,可以起相同的名字;
C:变量不能以数字作为开头;
DA和a在java中是同一个变量;
答案:C5题:下列说法正确的是
AJAVA程序的main方法必须写在类里面
BJAVA程序的main方法中如果只有一条语句,可以不用{}(大括号)括起来
CJAVA程序中可以有多个main方法
DJAVA程序中类名必须与文件名一样
答案:A二、问答题第1题:小红马和小黑马生的小马几条腿
答案:4条腿第2题:为什么好马不吃回头草
答案:后面的草没了第3题:铁棒打头疼还是木棒打头疼
答案:头最疼第4题:什么床不能睡觉
答案:牙床考生:岳云鹏
考号:1000001921051
--------------------------------------------
一、选择题第1题:变量命名规范说法正确的是
AA和a在java中是同一个变量;
B:变量不能以数字作为开头;
C:变量由字母、下划线、数字、$符号随意组成;
D:不同类型的变量,可以起相同的名字;
答案:B2题:下列说法正确的是
AJAVA程序的main方法中如果只有一条语句,可以不用{}(大括号)括起来
BJAVA程序中可以有多个main方法
CJAVA程序中类名必须与文件名一样
DJAVA程序的main方法必须写在类里面
答案:D3题:JAVA所定义的版本中不包括
AJAVA2 SE
BJAVA2 Card
CJAVA2 HE
DJAVA2 ME
EJAVA2 EE
答案:C4题:以下()不是合法的标识符
ASTRING
B:x3x;
C:de$f
Dvoid
答案:D5题:表达式(11+3*8)/4%3的值是
A31
B2
C0
D1
答案:B二、问答题第1题:为什么好马不吃回头草
答案:后面的草没了第2题:什么床不能睡觉
答案:牙床第3题:小红马和小黑马生的小马几条腿
答案:4条腿第4题:铁棒打头疼还是木棒打头疼
答案:头最疼考生:于谦
考号:1000001921987
--------------------------------------------
一、选择题第1题:表达式(11+3*8)/4%3的值是
A31
B2
C1
D0
答案:B2题:以下()不是合法的标识符
A:x3x;
B:de$f
CSTRING
Dvoid
答案:D3题:下列说法正确的是
AJAVA程序中可以有多个main方法
BJAVA程序的main方法中如果只有一条语句,可以不用{}(大括号)括起来
CJAVA程序的main方法必须写在类里面
DJAVA程序中类名必须与文件名一样
答案:C4题:JAVA所定义的版本中不包括
AJAVA2 Card
BJAVA2 SE
CJAVA2 HE
DJAVA2 ME
EJAVA2 EE
答案:C5题:变量命名规范说法正确的是
A:不同类型的变量,可以起相同的名字;
B:变量由字母、下划线、数字、$符号随意组成;
C:变量不能以数字作为开头;
DA和a在java中是同一个变量;
答案:C二、问答题第1题:为什么好马不吃回头草
答案:后面的草没了第2题:铁棒打头疼还是木棒打头疼
答案:头最疼第3题:小红马和小黑马生的小马几条腿
答案:4条腿第4题:什么床不能睡觉
答案:牙床Disconnected from the target VM, address: '127.0.0.1:50335', transport: 'socket'Process finished with exit code 0

总结

  • 以上的实际场景模拟了原型模式在开发中᯿构的作⽤,但是原型模式的使⽤频率确实不是很⾼。如果有⼀些特殊场景需要使⽤到,也可以按照此设计模式进⾏优化。
  • 另外原型设计模式的优点包括;便于通过克隆⽅式创建复杂对象、也可以避免᯿复做初始化操作、不需要与类中所属的其他类耦合等。但也有⼀些缺点如果对象中包括了循环引⽤的克隆,以及类中深度使⽤对象的克隆,都会使此模式变得异常麻烦。
  • 终究设计模式是⼀整套的思想,在不同的场景合理的运⽤可以提升整体的架构的质量。永远不要想着去硬凑设计模式,否则将会引起过渡设计,以及在承接业务反复变化的需求时造成浪费的开发和维护成本。
  • 初期是代码的优化,中期是设计模式的使⽤,后期是把控全局服务的搭建。不断的加强⾃⼰对全局能⼒的把控,也加深⾃⼰对细节的处理。可上可下才是⼀个程序员最佳处理⽅式,选取做合适的才是最好的选择。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/305895.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024mathorcup数学建模A 题思路分析-移动通信网络中 PCI 规划问题

# 1 赛题 A 题 移动通信网络中 PCI 规划问题 物理小区识别码(PCI)规划是移动通信网络中下行链路层上&#xff0c;对各覆盖 小区编号进行合理配置&#xff0c;以避免 PCI 冲突、 PCI 混淆以及 PCI 模 3 干扰等 现象。 PCI 规划对于减少物理层的小区间互相干扰(ICI)&#xff0c;增…

达梦使用disql登录数据库显示“未连接”

基础环境 操作系统&#xff1a;Red Hat Enterprise Linux Server release 7.9 (Maipo) 数据库版本&#xff1a;DM Database Server 64 V8 架构&#xff1a;单实例问题&#xff1a;达梦数据库在使用disql登录时&#xff0c;显示“未连接”。 指定了IP和端口号还是连接异常。 […

EFcore 6 连接oracle19 WinForm vs2022

用EFcore访问Oracle&#xff0c;终于不需要Oracle的什么安装包了&#xff0c;直接在VS2022中就可以轻松搞定。在csdn上看到一哥们的帖子&#xff0c;测试了一下&#xff0c;发现很方便。使用的场景是&#xff1a;VS2022中EFcore6。经过测试&#xff0c;同 Navicat Premium 16比…

【C++进阶】C++异常详解

C异常 一&#xff0c;传统处理错误方式二&#xff0c;C处理的方式三&#xff0c;异常的概念四&#xff0c;异常的使用4.1 异常和捕获的匹配原则4.2 函数调用链中异常栈展开匹配原则4.3 异常的重新抛出&#xff08;异常安全问题&#xff09;4.4 RAII思想在异常中的作用 五&#…

标定系列——Ubuntu18.04下opencv-4.5.3与opencv_contrib-4.5.3源码编译(二十)

Ubuntu18.04下opencv-4.5.3与opencv_contrib-4.5.3源码编译 说明下载安装步骤1.更新2.安装必要的依赖包3.下载源码包并解压4.终端运行如下命令5.添加配置路径6.验证安装是否成功 说明 Ubuntu18.04下对opencv-4.5.3与opencv_contrib-4.5.3源码编译 下载 CSDN下载 安装步骤 …

手写ArrrayList

需求 自定义的MyArrayList import java.util.Arrays; import java.util.Objects;public class MyArrayList<E> {private Object[] elementData ; // 存储元素的数组private int size; // 记录 的元素个数private static final int DEFAULT_CAPACITY 10; // 默认容量// …

提升编程效率的秘密武器:IntelliJ IDEA

IntelliJ IDEA的基本介绍 正如一个故事的开头&#xff0c;我们从一个名字开始 - IntelliJ IDEA。这是一个在程序员中广受欢迎的集成开发环境&#xff08;IDE&#xff09;&#xff0c;由捷克公司JetBrains开发。它的名字听起来有些复杂&#xff0c;但实际上&#xff0c;它的功能…

蓝桥杯备考day4

1.1 二分查找模板 bool check(int x) {// 进行某些操作 } // 二分查找函数 int binarySearch() {int l 1, r n; // 初始化左右边界while (r - l > 1) // 当右边界与左边界相差大于1时{int mid (l r) >> 1; // 取中间位置if (check(mid)) // 如果满足条件r mid; …

第十一届蓝桥杯大赛第二场省赛试题 CC++ 研究生组-七段码

#include<iostream> using namespace std; const int N 10, M 7; int e[N][N] {0}, f[N], open[N];//e[i][j]表示i和j之间是否连通&#xff1b;f[i]表示结点i的父节点&#xff1b;open[i] 1表示结点i打开&#xff0c;0表示关闭 long long ans 0;int find(int x){if(…

PP-LCNet:一种轻量级CPU卷积神经网络

PP-LCNet: A Lightweight CPU Convolutional Neural Network 最近看了一个新的分享&#xff0c;在图像分类的任务上表现良好&#xff0c;具有很高的实践意义。 论文&#xff1a; https://arxiv.org/pdf/2109.15099.pdf项目&#xff1a; https://github.com/PaddlePaddle/Padd…

AMD Tensile 简介与示例

按照知其然&#xff0c;再知其所以然的认知次序进行 1&#xff0c;下载代码 git clone --recursive https://github.com/ROCm/Tensile.git 2&#xff0c;安装 Tensile cd Tensile mkdir build cd build ../Tensile/bin/Tensile ../Tensile/Configs/rocblas_dgemm_nn_asm_full…

微信小程序(六)定位搜索

一、引言 作者上一章讲了微信小程序的地图实现微信小程序&#xff08;五&#xff09;地图-CSDN博客&#xff0c;但是还有一个功能是和地图紧密结合的&#xff0c;那就是位置搜索定位&#xff0c;这里作者讲讲实现和原理&#xff0c;包括城市筛选。 二、定位搜索实现 1、位置搜…

mysql查看数据库表容量大小

【推荐】单表行数超过 500 万行或者单表容量超过 2GB&#xff0c;才推荐进行分库分表。 说明&#xff1a;如果预计三年后的数据量根本达不到这个级别&#xff0c;请不要在创建表时就分库分表。 1. 查询所有数据库记录数和容量 SELECTtable_schema AS 数据库,SUM(table_rows) …

用vue.js写案例——ToDoList待办事项 (步骤和全码解析)

目录 一.准备工作 二.编写各个组件的页面结构 三.实现初始任务列表的渲染 四.新增任务 五.删除任务 六.展示未完成条数 七.切换状态-筛选数据 八.待办事项&#xff08;全&#xff09;代码 一.准备工作 在开发“ToDoList”案例之前&#xff0c;需要先完成一些准备工作&a…

图像处理与视觉感知---期末复习重点(7)

文章目录 一、图像压缩1.1 三种冗余1.2 模型1.3 信息测量 二、无误差压缩2.1 哈夫曼编码2.1.1 步骤2.1.2 例题 2.2 算术编码 三、变换编码 一、图像压缩 1.1 三种冗余 1. 三种基本的是数据冗余为&#xff1a;编码冗余、像素间冗余、心理视觉冗余。 2. 编码冗余&#xff1a;如果…

蓝桥杯——玩具蛇

题目 小蓝有—条玩具蛇&#xff0c;一共有16节&#xff0c;上面标着数字1至16。每—节都是一个正方形的形状。相邻的两节可以成直线或者成90度角。 小蓝还有一个44的方格盒子&#xff0c;用于存放玩具蛇&#xff0c;盒子的方格上依次标着字母A到Р共16个字母。 小蓝可以折叠自…

浙大恩特客户资源管理系统 i0004_openFileByStream.jsp 任意文件读取漏洞复现

0x01 产品简介 浙大恩特客户资源管理系统是一款针对企业客户资源管理的软件产品。该系统旨在帮助企业高效地管理和利用客户资源,提升销售和市场营销的效果。 0x02 漏洞概述 浙大恩特客户资源管理系统 i0004_openFileByStream.jsp接口处存在任意文件读取漏洞,未经身份验证攻…

快速开始vue3

版本 node (20.11.1)vue3 (3.4.21) 脚手架创建项目并运行 安装脚手架并创建项目 npm create vuelatest这一指令将会安装并执行 create-vue&#xff0c;它是 Vue 官方的项目脚手架工具 2&#xff09; 安装以下进行选择 ## 配置项目名称 √ Project name: vue3_test ## 是否…

网络编程基础

目录 【1】网络编程&#xff1a; ​【2】通信两个重要的要素&#xff1a;IPPORT 【3】设备之间进行传输的时候&#xff0c;必须遵照一定的规则 ---》通信协议&#xff1a; 【4】TCP协议&#xff1a;可靠的 建立连接&#xff1a; 三次握手 ​编辑释放连接&#xff1a;四次挥…

Python生成图片和音频验证码

captcha是pyhton的一个模块&#xff0c;用来生成图片和音频验证码。 安装 pip install captcha使用 from captcha.audio import AudioCaptcha from captcha.image import ImageCaptcha# 加载声音和字体 audio AudioCaptcha(voicedir/path/to/voices) image ImageCaptcha(…