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

news2025/2/24 15:29:29

原型模式

    • 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;

    @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-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 {

    @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所定义的版本中不包括
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;
    }

    @Override
    public 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;
    }

    @Override
    public 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 {

    @Test
    public 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.coloradmin.cn/o/1585943.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

实验9 内置对象application

一、实验目的 掌握怎样在JSP中使用内置对象application 二、实验项目内容&#xff08;实验题目&#xff09; 编写代码&#xff0c;掌握application的用法。【参考课本例题4-16 留言板 】 三、源代码以及执行结果截图&#xff1a; example4_16.jsp <% page language"…

python——条件语句

概念 条件语句&#xff0c;简单的理解就是 满足条件执行这些代码&#xff0c;不满足则执行另一些代码 语法 #mermaid-svg-ITs2kv8f87vZuQhT {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-ITs2kv8f87vZuQhT .erro…

【网站项目】鲜花销售微信小程序

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

excle如何设置自动显示星期几

目录 一.目的 二.范例结果 三.处理方式 一.目的 excle依据日期&#xff0c;设置后自动显示星期几。 二.范例结果 三.处理方式 公式1TEXT(XXX,DDDD) 公式2TEXT(XXX,DDD)

YoloV8改进策略:Block改进|轻量级的Mamba打造优秀的YoloV8|即插即用,简单易懂|附Block结构图(独家原创)更新中。。。。。

摘要 无Mamba不狂欢&#xff0c;今天给大家带来一个基于轻量级Mamba的改进。模块简单易懂&#xff0c;即插即用&#xff01; 带领大家去征服更高的领域。 论文&#xff1a;《LightM-UNet&#xff1a;Mamba 辅助的轻量级 UNet 用于医学图像分割》 https://arxiv.org/pdf/2403…

欢迎加入PenPad Season 2 ,获得勋章以及海量 Scroll 生态权益

PenPad 是 Scroll 生态中的首个 LaunchPad 平台&#xff0c;该平台继承了 Scroll 生态的技术优势&#xff0c;具备包括隐私在内的系列特点&#xff0c;同时且也被认为是 Scroll 生态最重要的价值入口之一。Penpad 与 Scroll 官方始终保持着合作&#xff0c;同时该项目自启动以来…

解决VM报错:不支持虚拟化的 amd-v/rvi

安装了VMware之后&#xff0c;想测试一下虚拟机嵌套。在勾选虚拟机CPU的虚拟化AMD-V/RVI之后&#xff0c;竟然无法启动&#xff0c;提示“此平台不支持虚拟化的 amd-v/rvi”。 上网找了一下资料&#xff0c;发现是因为Hyper-V与VMware冲突以及Windows Defender的内核隔离导致的…

【基础知识】计算机国企爱考的二进制知识、大学生必须掌握

二进制与十进制相互转换的详细过程及例题解析 二进制和十进制是两种常用的数制系统。二进制系统仅使用0和1两个数字&#xff0c;而十进制系统则使用0到9的十个数字。在计算机科学和数字电路设计等领域&#xff0c;经常需要在这两种数制之间进行转换。本文将详细介绍二进制与十…

(二)ffmpeg 下载安装以及拉流推流示例

一、ffmpeg下载安装 官网&#xff1a;https://www.ffmpeg.org/ 源码下载地址&#xff1a;https://www.ffmpeg.org/download.html#releases 下载源码压缩包 下载完成之后解压并在该目录下打开命令窗口 安装依赖环境&#xff1a; sudo apt-get install build-essential nasm …

组合数学<1>——组合数学基础

今天我们聊聊组合数学。(本期是给刚刚学习组合数学的同学看的&#xff0c;dalao们可以自行忽略) 建议:不会求逆元的出门左转数论<2>&#xff0c;不会数论的出门右转数论<1>。 加乘原理 加乘原理小学奥数就有。 总的来说:加法原理:分类;乘法原理:分步 比如说&a…

OJ 【难度1】【Python】完美字符串 扫雷 A-B数对 赛前准备 【C】精密计时

完美字符串 题目描述 你可能见过下面这一句英文&#xff1a; "The quick brown fox jumps over the lazy dog." 短短的一句话就包含了所有 2626 个英文字母&#xff01;因此这句话广泛地用于字体效果的展示。更短的还有&#xff1a; "The five boxing wizards…

【我的小工具】生成React页面类

有了数据表的结构信息&#xff0c;就能生成React 的页面类&#xff0c;快捷方便。 生成界面如下&#xff1a; 生成的React FrmUser.js页面如下&#xff1a; 只需再写里面的操作逻辑代码。

存储革新:下一代低功耗PCM相变存储器

引言 由于Optane&#xff08;实质为PCM相变存储器&#xff09;被intel放弃以后&#xff0c;小编一直在关注业内有关SCM存储级内存&#xff08;PCM、ReRAM等&#xff09;相关的研究进展。比如之前发布的内容&#xff0c;供存储随笔的读者参考&#xff01; 字节跳动入局存储内存…

如何应用电桥电路的原理?

电桥电路是一种常用的测量技术&#xff0c;它利用了四个电阻的网络来检测电路的平衡状态。在平衡状态下&#xff0c;电桥的输出电压为零&#xff0c;这种特性使得电桥电路非常适合于精确测量电阻、电感、电容等电气参数&#xff0c;以及用于传感器和测量设备中。以下是电桥电路…

第十二届蓝桥杯真题做题笔记

2、卡片 笔记&#xff1a; 直接巧用排列组合求解即可&#xff1a; 我们通过对样例说明进行分析可知&#xff1a;想要分给n个小孩&#xff0c;那么我们就需要满足C(K, 2) K > n才能满足。 #include<bits/stdc.h> using namespace std;int com(int up, int down){i…

从文字到思维:呆马GPT在人工智能领域的创新之旅

引言 生成式预训练变换器&#xff08;Generative Pre-trained Transformer&#xff0c;简称GPT&#xff09;领域是人工智能技术中的一大革新。自OpenAI推出第一代GPT以来&#xff0c;该技术经历了多代发展&#xff0c;不断提升模型的规模、复杂度和智能化程度。GPT模型通过在大…

AI大模型日报#0411:国内首款音乐大模型、面壁智能数亿融资、MyScale AI开源

导读&#xff1a; 欢迎阅读《AI大模型日报》&#xff0c;内容基于Python爬虫和LLM自动生成。目前采用“文心一言”生成了每条资讯的摘要。 ​标题: 大模型做时序预测也很强&#xff01;华人团队激活LLM新能力&#xff0c;超越一众传统模型实现SOTA 摘要: 大语言模型通过新提…

从零开始学Spring Boot系列-集成MyBatis-Plus

在Spring Boot应用开发中&#xff0c;MyBatis-Plus是一个强大且易于使用的MyBatis增强工具&#xff0c;它提供了很多实用的功能&#xff0c;如代码生成器、条件构造器、分页插件等&#xff0c;极大地简化了MyBatis的使用和配置。本篇文章将指导大家如何在Spring Boot项目中集成…

git学习 1

打开自己想要存放git仓库的文件夹&#xff0c;右键打开git bush&#xff0c;用git init命令建立仓库 用 ls -a(表示全都要看&#xff0c;包括隐藏的)可以看到git仓库 也可以用 git clone 接github链接&#xff08;点code选项里面会给链接&#xff0c;结尾是git的那个&#xf…

如何用Java后端处理JS.XHR请求

Touching searching engine destroies dream to utilize php in tomcat vector.The brave isn’t knocked down&#xff0c;turn its path to java back-end. Java Servlet Bible schematic of interaction between JS front-end and Java back-end Question 如何利用Java…