原型模式
- 1、原型模式
- 1-1、应用场景
- 1-2、举个 软栗子
- 1-3、举个 硬栗子
- 1-3、举个实务栗子
学习原型模式的目的:原型模式的目的在于通过复制现有的实例来创建新的对象,以避免通过构造函数创建对象时可能带来的性能开销,同时可以控制对象的深度复制。
1、原型模式
定义:
在Java中,原型模式(Prototype Pattern)是一种创建型设计模式,其主要目的是通过使用已有对象作为原型,克隆并生成新的对象,而无需显式地使用构造函数来创建对象。原型模式的核心思想是通过克隆(Clone)现有的对象来创建新的对象,这种克隆可以是浅克隆(Shallow Clone)或深克隆(Deep Clone)。
- 浅克隆:通过调用对象的
clone()
方法复制对象,得到一个新的对象实例,新对象与原对象共享部分数据。对于引用类型的成员变量,浅克隆只复制其引用而不创建新的对象。- 深克隆:通过自定义克隆逻辑,递归地复制对象及其引用类型的成员变量,得到一个完全独立的新对象实例。深克隆会逐层复制对象及其引用的对象,保证新对象与原对象的数据完全独立。
要实现原型模式,需要满足以下条件:
- 被克隆的类必须实现
Cloneable
接口,这个接口是一个标记接口,表明该类可以被克隆。- 在被克隆的类中覆写
clone()
方法,实现对象的克隆操作。- 在使用原型模式时,通过调用对象的
clone()
方法创建新的对象。
通过使用原型模式,可以避免直接使用构造函数复杂地创建对象,提高对象的创建效率和灵活性。
需要注意的是,在克隆过程中如果涉及到引用类型的成员变量,如果要实现深克隆,需要对引用类型的成员变量也进行克隆操作,确保数据的独立性和完整性。
总的来说,原型模式通过克隆现有对象来创建新的对象,实现了对象的复制和创建的分离,提高了对象创建的效率和灵活性。
1-1、应用场景
Java中的原型模式适用于以下业务应用场景:
- 对象的创建成本高:当需要创建一个对象的成本很高,比如需要初始化大量的数据、进行复杂的计算或者远程调用等,可以使用原型模式。
通过复制已有的对象,避免重复的创建过程,提高性能和效率
。- 对象的创建过程复杂:有些对象的创建过程比较复杂,涉及到多个步骤或者依赖其他对象。在这种情况下,可以使用原型模式将一个已经初始化好的对象作为原型,通过复制来创建新对象,
避免创建过程的复杂性和依赖关系的传递
。- 动态配置对象:当需要根据用户的不同配置动态生成对象时,可以使用原型模式。通过定义一个原型对象,根据不同的配置参数进行复制,
快速生成多个符合用户需求的对象
。- 多线程环境下的对象创建:在多线程环境下,对象的创建可能会引起线程安全的问题。
使用原型模式,可以避免多个线程同时创建对象
,从而保证对象的安全性和一致性。总的来说,原型模式适用于那些创建成本高、创建过程复杂、动态配置或需要在多线程环境下创建对象的场景。通过原型模式,可以通过复制现有的对象来创建新对象,提高性能、简化对象的创建过程,并保证对象的安全性和一致性。
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;
@Override
public 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;
@Override
public 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;
@Override
public 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-3、举个实务栗子
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 {
@Test
public 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所定义的版本中不包括
A:JAVA2 EE
B:JAVA2 Card
C:JAVA2 ME
D:JAVA2 HE
E:JAVA2 SE
答 案:D
第2题:下列说法正确的是
A:JAVA程序的main⽅法必须写在类⾥⾯
B:JAVA程序中可以有多个main⽅法
C:JAVA程序中类名必须与⽂件名⼀样
D:JAVA程序的main⽅法中如果只有⼀条语句,可以不⽤{}(⼤括号)括起来
答 案:A
第3题:变量命名规范说法正确的是
A:变量由字⺟、下划线、数字、$符号随意组成;
B:变量不能以数字作为开头;
C:A和a在java中是同⼀个变量;
D:不同类型的变量,可以起相同的名字;
答 案:B
第4题:以下()不是合法的标识符
A:STRING
B:x3x;
C:void
D:de$f
答 案:C
第5题:表达式(11+3*8)/4%3的值 是
A:31
B:0
C:1
D:2
答 案: D
⼆、问答题
第1题:⼩红⻢和⼩⿊⻢⽣的⼩⻢⼏条腿
答 案:4条腿
第2题:铁棒打头疼还是⽊棒打头疼
答 案:头最疼
第3题:什么床不能睡觉
答 案:⽛床
第4题:为什么好⻢不吃回头草
答 案:后⾯的草没了
考⽣:⾖⾖
考号:1000001921051
--------------------------------------------
⼀、选择题
第1题:JAVA所定义的版本中不包括
A:JAVA2 EE
B:JAVA2 Card
C:JAVA2 ME
D:JAVA2 HE
E:JAVA2 SE
答 案:D
第2题:下列说法正确的是
A:JAVA程序的main⽅法必须写在类⾥⾯
B:JAVA程序中可以有多个main⽅法
C:JAVA程序中类名必须与⽂件名⼀样
D:JAVA程序的main⽅法中如果只有⼀条语句,可以不⽤{}(⼤括号)括起来
答 案:A
第3题:变量命名规范说法正确的是
A:变量由字⺟、下划线、数字、$符号随意组成;
B:变量不能以数字作为开头;
C:A和a在java中是同⼀个变量;
D:不同类型的变量,可以起相同的名字;
答 案:B
第4题:以下()不是合法的标识符
A:STRING
B:x3x;
C:void
D:de$f
答 案:C
第5题:表达式(11+3*8)/4%3的值 是
A:31
B:0
C:1
D:2
答 案: D
⼆、问答题
第1题:⼩红⻢和⼩⿊⻢⽣的⼩⻢⼏条腿
答 案:4条腿
第2题:铁棒打头疼还是⽊棒打头疼
答 案:头最疼
第3题:什么床不能睡觉
答 案:⽛床
第4题:为什么好⻢不吃回头草
答 案:后⾯的草没了
考⽣:⼤宝
考号:1000001921987
--------------------------------------------
⼀、选择题
第1题:JAVA所定义的版本中不包括
A:JAVA2 EE
B:JAVA2 Card
C:JAVA2 ME
D:JAVA2 HE
E:JAVA2 SE
答 案:D
第2题:下列说法正确的是
A:JAVA程序的main⽅法必须写在类⾥⾯
B:JAVA程序中可以有多个main⽅法
C:JAVA程序中类名必须与⽂件名⼀样
D:JAVA程序的main⽅法中如果只有⼀条语句,可以不⽤{}(⼤括号)括起来
答 案:A
第3题:变量命名规范说法正确的是
A:变量由字⺟、下划线、数字、$符号随意组成;
B:变量不能以数字作为开头;
C:A和a在java中是同⼀个变量;
D:不同类型的变量,可以起相同的名字;
答 案:B
第4题:以下()不是合法的标识符
A:STRING
B:x3x;
C:void
D:de$f
答 案:C
第5题:表达式(11+3*8)/4%3的值 是
A:31
B:0
C:1
D:2
答 案: D
⼆、问答题
第1题:⼩红⻢和⼩⿊⻢⽣的⼩⻢⼏条腿
答 案:4条腿
第2题:铁棒打头疼还是⽊棒打头疼
答 案:头最疼
第3题:什么床不能睡觉
答 案:⽛床
第4题:为什么好⻢不吃回头草
答 案:后⾯的草没了
进程已结束,退出代码0
以上就是三位考试的试卷; 花花、 ⾖、⼤宝,每个⼈的试卷内容是⼀样的这没问题,但是三个⼈的题⽬以及选项顺序都是⼀样
,就没有达到我们说希望的乱序要求
。⽽且以上这样的代码⾮常难扩展
,随着题⽬的不断的增加以及乱序功能的补充,都会让这段代码变
得越来越混乱