探索设计模式的魅力:一次设计,多次利用,深入理解原型模式的设计艺术

 

    原型模式是一种设计模式,属于创建型模式的一种,它用于创建重复的对象,同时又能保持性能。在原型模式中,通过复制现有对象的原型来创建新对象,而不是通过实例化类来创建对象。这样做可以避免耗费过多的资源开销,特别是在对象的创建过程比较复杂或耗时的情况下。

    在原型模式中,原型对象实现一个克隆方法(Clone)用于复制自身,当需要创建新对象时,就可以通过克隆原型对象来得到一个新的对象副本。原型模式通常包括浅拷贝和深拷贝两种形式,浅拷贝只复制对象本身,而深拷贝则会连同对象引用的其他对象一起复制,因此能够得到完全独立的新对象。

    原型模式可以在需要大量创建相似对象的场景中发挥作用,它能够提高对象的创建效率,同时也能够减少对类的直接依赖,使系统结构更灵活。

  关键角色有两个:

  1. 原型(Prototype):定义用于复制现有对象的接口,通常包含一个克隆方法,用于返回一个克隆对象。
  2. 具体原型(Concrete Prototype):实现原型接口,实现克隆方法来复制自身。

  优点:

  1. 减少对象创建时间:原型模式通过复制现有对象来创建新对象,避免了昂贵的对象创建过程,特别是在需要频繁创建相似对象时,可以大大减少对象创建的时间和开销。
  2. 简化对象创建过程:原型模式封装了对象的创建过程,客户端无需关心具体的创建细节,使得对象创建变得更加简单,提高了系统的可维护性和扩展性。
  3. 动态增加或减少原型:原型模式允许动态地添加或删除原型,使得系统更加灵活,能够根据需求动态创建新的原型对象。

  缺点:

  1. 需要理解原型对象:在使用原型模式时,需要确保原型对象的克隆方法能够正确地复制对象的状态,有时需要深度复制而不是浅复制,这需要额外的处理和理解。
  2. 难以保持一致性:原型模式可能会造成系统中的一些对象克隆出来之后状态难以保持一致,特别是涉及到对象之间的引用关系时,需要特别小心处理。

  本质体现了两个关键点:

  1. 原型:具备复制能力的对象,它是创建新对象的模板。原型可以是一个接口、抽象类或具体类,关键是它需要提供克隆自身的方法。
  2. 克隆:根据原型对象复制出来的新对象。克隆过程可以是浅复制(只复制对象本身)或者深复制(复制对象和其引用的对象)。

    原型模式的本质是通过复制现有对象来创建新对象,从而封装了对象的创建过程,提供了一种灵活、高效的对象创建方式。

目录

一、案例

1.1 不用模式来实现

1.2 有何问题

1.3 原型模式重构代码

1.4 完美实现 

三、模式讲解

3.1 功能

3.2 原型模式的结构和说明

 3.3 几种工厂模式总结


一、案例

场景:前段时间有一个考试,发现在一个教室里考试的试卷有ABCDEFG卷,座位上前后左右人的卷子跟我的都不一样。考完后听老师说,ABCDEFG卷的题目是一样的,意思就是题目是一样的,题目的顺序是打乱的甚至同一个题的选项的顺序也是打乱的。

    下面我们从无到有来感受原型模式的设计艺术。

1.1 不用模式来实现

  选择题类:

@Data
public class ChoiceQuestion {/*** 选择题题目*/private String title;/*** 选项*/private Map<String, String> options;/*** 答案*/private String key;public ChoiceQuestion(){}public ChoiceQuestion(String title, Map<String, String> options, String key) {this.title = title;this.options = options;this.key = key;}
}

  填空题类:

@Data
public class Completion {/*** 填空题题目*/private String title;/*** 答案*/private List<String> key;public Completion() {}public Completion(String title, List<String> key) {this.title = title;this.key = key;}
}

  试卷类:

public class TestPaper {/*** 创建试卷* @param name 考生姓名* @param code 考生编号* @return*/public String createPaper(String name, String code) {// 这里举例有两道选择题和两道填空题List<ChoiceQuestion> choiceQuestionList = new ArrayList<>(2);List<Completion> completionList = new ArrayList<>(2);// 初始化选择题Map<String, String> choiceMap1 = new HashMap<>();choiceMap1.put("A", "1");choiceMap1.put("B", "2");choiceMap1.put("C", "3");choiceMap1.put("D", "4");Map<String, String> choiceMap2 = new HashMap<>();choiceMap2.put("A", "1");choiceMap2.put("B", "2");choiceMap2.put("C", "3");choiceMap2.put("D", "4");choiceQuestionList.add(new ChoiceQuestion("1 + 1 = ", choiceMap1, "B"));choiceQuestionList.add(new ChoiceQuestion("3 + 1 = ", choiceMap2, "D"));// 初始化填空题List<String> comList1 = new ArrayList<>();comList1.add("富强");comList1.add("民主");List<String> comList2 = new ArrayList<>();comList2.add("51");comList2.add("21");completionList.add(new Completion("任意列举出两个社会主义核心价值观", comList1));completionList.add(new Completion("2008年中国获得金牌数___枚,银牌___枚", comList2));String newLine = "\n";// 组装试卷StringBuffer strBuff = new StringBuffer();strBuff.append("考生:").append(name).append(newLine).append("考号:").append(code).append(newLine).append("一、选择题").append(newLine);for (ChoiceQuestion choiceQuestion : choiceQuestionList) {strBuff.append(choiceQuestion.getTitle()).append(newLine);for (Map.Entry<String, String> entry : choiceQuestion.getOptions().entrySet()) {String key = entry.getKey();String value = entry.getValue();strBuff.append(key).append(":").append(value).append(newLine);}strBuff.append("答案:").append(choiceQuestion.getKey()).append(newLine);}for (Completion completion : completionList) {strBuff.append(completion.getTitle()).append(newLine);strBuff.append("答案:");for (String anster : completion.getKey()) {strBuff.append("  ").append(anster);}}return strBuff.toString();}
}

  测试客户端:

public class Client {public static void main(String[] args) {TestPaper testPaper = new TestPaper();System.out.println(testPaper.createPaper("张三", "ACCDF0001"));System.out.println(testPaper.createPaper("李四", "ACCDF0002"));System.out.println(testPaper.createPaper("王五", "ACCDF0003"));}
}

  运行结果如下:

考生:张三
考号:ACCDF0001
一、选择题
1 + 1 = 
A:1
B:2
C:3
D:4
答案:B
3 + 1 = 
A:1
B:2
C:3
D:4
答案:D
任意列举出两个社会主义核心价值观
答案:  富强  民主
2008年中国获得金牌数___枚,银牌___枚
答案:  51  21考生:李四
考号:ACCDF0002
一、选择题
1 + 1 = 
A:1
B:2
C:3
D:4
答案:B
3 + 1 = 
A:1
B:2
C:3
D:4
答案:D
任意列举出两个社会主义核心价值观
答案:  富强  民主
2008年中国获得金牌数___枚,银牌___枚
答案:  51  21考生:王五
考号:ACCDF0003
一、选择题
1 + 1 = 
A:1
B:2
C:3
D:4
答案:B
3 + 1 = 
A:1
B:2
C:3
D:4
答案:D
任意列举出两个社会主义核心价值观
答案:  富强  民主
2008年中国获得金牌数___枚,银牌___枚
答案:  51  21

  一坨坨代码很快就实现了,看上去很简单。 

        

1.2 有何问题

  • 以上呢就是三位考试的试卷; 线三 、李四和王五 ,每个⼈人的试卷内容是⼀一样的这没问题,但是三个人的题⽬以及选项顺序和答案都是一样的,但是没有达到我们的场景中“ABCDEFG卷题目顺序和选项顺序都不一样”的目的。
  • 以上代码⾮非常难扩展,随着题⽬目的不不断的增加以及乱序功能的补充,都会让这段代码变 得越来越混乱。 

         

1.3 原型模式重构代码

原型模式解决的痛点:创建⼤量重复的类,⽽我们的场景就需要给不不同的用户都创建相同的试卷,但这些试卷的题目不便于每次都从库中获取,甚至有时候需要从远程的RPC中获取。这样都是⾮常耗时的,⽽且随着创建对象的增多将严重影响效率。

        

 用原型模式注意要点:

  • 原型对象的接口:在原型模式中,原型对象需要实现一个克隆方法(Clone),该方法用于复制自身并返回一个新的对象副本。这需要确保原型对象具有适当的接口和克隆能力。

  • 浅拷贝和深拷贝:在实现克隆方法时,需要考虑对象中是否存在引用类型的成员变量。浅拷贝只会复制对象本身,而不会复制对象包含的引用类型成员变量,这意味着原型对象和克隆对象会引用同一个引用类型的成员变量。深拷贝则会复制对象本身以及其引用类型的成员变量,使得原型对象和克隆对象完全独立。选择浅拷贝还是深拷贝取决于实际需求和设计考虑。

  • 创建新对象的方式:在使用原型模式创建新对象时,可以通过调用原型对象的克隆方法,或者使用原型管理器(Prototype Manager)来获取新对象。原型管理器可以维护一组原型对象,并根据需要返回相应的克隆对象。

  • 修改克隆对象:当获得克隆对象后,可以根据需要对克隆对象做进一步的修改。这使得每个克隆对象都可以根据需求进行个性化的定制。

         

  代码改造

  添加选项答案类:

@Data
public class Option {/*** 选项*/private Map<String, String> options;/*** 答案*/private String key;public Option(Map<String, String> options, String key) {this.options = options;this.key = key;}
}

  添加选项乱序工具类:

public class RandomUtil {/*** 乱序Map元素,记录对应答案key* @param option 题⽬选项 * @param key 答案* @return Option 乱序后 {A=c., B=d., C=a., D=b.} */static public Option random(Map<String, String> option, String key) {Set<String> keySet = option.keySet();ArrayList<String> keyList = new ArrayList<>(keySet);Collections.shuffle(keyList);HashMap<String, String> optionNew = new HashMap<>();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 Option(optionNew, keyNew);}
}

  添加题库类

@Data
public class QuestionBank implements Cloneable {/*** 试卷序号*/private String seq;/*** 考生姓名*/private String name;/*** 考生考号*/private String code;private ArrayList<ChoiceQuestion> choiceQuestionList = new ArrayList<>(2);private ArrayList<Completion> completionList = new ArrayList<>(2);/*** 实现中题目可能来自数据库中,这里示例便直接写死*/public QuestionBank() {// 初始化选择题Map<String, String> choiceMap1 = new HashMap<>();choiceMap1.put("A", "1");choiceMap1.put("B", "2");choiceMap1.put("C", "3");choiceMap1.put("D", "4");Map<String, String> choiceMap2 = new HashMap<>();choiceMap2.put("A", "1");choiceMap2.put("B", "2");choiceMap2.put("C", "3");choiceMap2.put("D", "4");this.choiceQuestionList.add(new ChoiceQuestion("1 + 1 = ", choiceMap1, "B"));this.choiceQuestionList.add(new ChoiceQuestion("3 + 1 = ", choiceMap2, "D"));// 初始化填空题List<String> comList1 = new ArrayList<>();comList1.add("富强");comList1.add("民主");List<String> comList2 = new ArrayList<>();comList2.add("51");comList2.add("21");this.completionList.add(new Completion("任意列举出两个社会主义核心价值观", comList1));this.completionList.add(new Completion("2008年中国获得金牌数___枚,银牌___枚", comList2));}@Overridepublic QuestionBank clone() {try {QuestionBank questionBank = (QuestionBank) super.clone();questionBank.setChoiceQuestionList((ArrayList<ChoiceQuestion>) this.getChoiceQuestionList().clone());questionBank.setCompletionList((ArrayList<Completion>) this.getCompletionList().clone());// 打乱选择题Collections.shuffle(questionBank.getChoiceQuestionList());// 选择题的选项打乱for (ChoiceQuestion choiceQuestion : questionBank.getChoiceQuestionList()) {Option option = RandomUtil.random(choiceQuestion.getOptions(), choiceQuestion.getKey());choiceQuestion.setOptions(option.getOptions());choiceQuestion.setKey(option.getKey());}// 打乱填空题Collections.shuffle(questionBank.getCompletionList());return questionBank;} catch (Exception e) {throw new AssertionError();}}@Overridepublic String toString() {String newLine = "\r\n";StringBuffer strBuff = new StringBuffer();strBuff.append("考卷:").append(getSeq()).append(newLine).append("考生:").append(getName()).append(newLine).append("考号:").append(getCode()).append(newLine);strBuff.append("一、选择题").append(newLine);for (ChoiceQuestion choiceQuestion : getChoiceQuestionList()) {strBuff.append(choiceQuestion.getTitle()).append(newLine);for (Map.Entry<String, String> entry : choiceQuestion.getOptions().entrySet()) {String key = entry.getKey();String value = entry.getValue();strBuff.append(key).append(":").append(value).append(newLine);}strBuff.append("答案:").append(choiceQuestion.getKey()).append(newLine);}strBuff.append("一、填空题").append(newLine);for (Completion completion : getCompletionList()) {strBuff.append(completion.getTitle()).append(newLine);strBuff.append("答案:");for (String anster : completion.getKey()) {strBuff.append("  ").append(anster);}}return strBuff.toString();}
}

  试卷类改造:

@Data
public class TestPaper {/*** 题库*/QuestionBank questionBank = new QuestionBank();/*** 创建试卷* @param req 试卷序号* @param name 考生姓名* @param code 考生编号* @return*/public String createPaper(String req, String name, String code) {QuestionBank questionBankClone = this.getQuestionBank().clone();questionBankClone.setSeq(req);questionBankClone.setName(name);questionBankClone.setCode(code);return questionBankClone.toString();}
}

  改造测试客户端:

public class Client {public static void main(String[] args) {// 创建试卷工具类并初始化题库TestPaper testPaper = new TestPaper();// 创建张三System.out.println(testPaper.createPaper("A", "张三", "A0001"));System.out.println(testPaper.createPaper("B", "李四", "A0002"));System.out.println(testPaper.createPaper("C", "王五", "A0003"));}
}

  运行结果如下:

  (考卷A)

考卷:A
考生:张三
考号:A0001
一、选择题
1 + 1 = 
A:3
B:1
C:2
D:4
答案:C
3 + 1 = 
A:3
B:1
C:4
D:2
答案:C
一、填空题
2008年中国获得金牌数___枚,银牌___枚
答案:  51  21
任意列举出两个社会主义核心价值观
答案:  富强  民主

  (考卷B)

考卷:B
考生:李四
考号:A0002
一、选择题
3 + 1 = 
A:4
B:1
C:2
D:3
答案:A
1 + 1 = 
A:2
B:4
C:3
D:1
答案:A
一、填空题
2008年中国获得金牌数___枚,银牌___枚
答案:  51  21
任意列举出两个社会主义核心价值观
答案:  富强  民主

  (考卷C)

考卷:C
考生:王五
考号:A0003
一、选择题
1 + 1 = 
A:1
B:4
C:3
D:2
答案:D
3 + 1 = 
A:4
B:3
C:1
D:2
答案:A
一、填空题
任意列举出两个社会主义核心价值观
答案:  富强  民主
2008年中国获得金牌数___枚,银牌___枚
答案:  51  21

         

1.4 完美实现 

    从上面结果看出,A卷、B卷和C卷的题目和选项都被打乱了。再添加DEF等卷,就完美实现了案例场景下我座位中前后左右人的卷子与我不同的情况

        

三、模式讲解

本质:通过复制现有对象来创建新对象,从而封装了对象的创建过程,提供了一种灵活、高效的对象创建方式。

3.1 功能

    原型模式 的功能 实际上包含两个方面:

  • 一个是通过克隆来创建新的对象实例;
  • 另一个是为克隆出来的对象实例复制原型实例属性的值。

3.2 原型模式的结构和说明

  • Prototype:声明一个克隆自身的接口,用来约束想要克隆自己的类,要求它们都要实现这里定义的克隆方法。
  • ConcretePrototype:实现Prototype接口的类,这些类真正实现了克隆自身的功能。
  • Client:使用原型的客户端,首先获取到原型对象,然后通过原型实例克隆自身来创建新的对象实例。

        

 3.3 几种工厂模式总结

    工厂方法、抽象工厂、建造者模式和原型模式是常见的软件设计模式,它们各自解决了不同类型的设计问题。

  1. 工厂方法模式:
    工厂方法模式是一种创建型设计模式,它提供了一种将对象的创建和使用分离的方式。核心在于定义一个创建对象的接口,但将具体的创建过程延迟到子类中进行实现。

  2. 抽象工厂模式:
    抽象工厂模式也是一种创建型设计模式,它提供了一种创建一系列相关或相互依赖对象的接口,而无需指定具体的类。核心在于提供一个能够创建一系列产品的工厂接口,而具体的工厂类负责实现这个接口并创建具体的产品。

  3. 建造者模式:
    建造者模式是一种创建型设计模式,它将一个复杂对象的构建过程与其表示相分离,并允许使用相同的构建过程来创建不同的表示。核心在于将对象的构建过程分解成多个步骤,并提供一个统一的接口来组合这些步骤,从而创建复杂对象。

  4. 原型模式:
    原型模式是一种创建型设计模式,它允许通过复制现有对象来创建新对象,而不是通过实现一个特定的工厂方法来创建对象。核心在于通过克隆已有的对象来创建新的对象,从而避免了通过构造函数创建对象时的复杂性。

  区别:

  • 工厂方法模式和抽象工厂模式都是用于对象创建,但工厂方法模式关注于单个对象的创建,而抽象工厂模式关注于一系列相关对象的创建。
  • 建造者模式主要用于创建复杂对象,并且可以根据需要定制对象的不同表示,而工厂方法和抽象工厂模式更多地关注于对象的创建过程和对象的组合。
  • 原型模式主要用于通过复制来创建新对象,避免了直接通过构造函数创建对象时的复杂性,与工厂方法、抽象工厂和建造者模式的区别在于其实现方式和目的不同。

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

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

相关文章

docker compose安装milvus

下载对应版本的milvus-standalone-docker-compose.yml wget https://github.com/milvus-io/milvus/releases/download/v2.3.5/milvus-standalone-docker-compose.yml重新命令为docker-compose.yml mv milvus-standalone-docker-compose.yml docker-compose.yml启动milvus doc…

《WebKit 技术内幕》学习之九(4): JavaScript引擎

4 实践——高效的JavaScript代码 4.1 编程方式 关于如何使用JavaScript语言来编写高效的代码&#xff0c;有很多铺天盖地的经验分享&#xff0c;以及很多特别好的建议&#xff0c;读者可以搜索相关的词条&#xff0c;就能获得一些你可能需要的结果。同时&#xff0c;本节希望…

074:vue+mapbox 加载here地图(影像瓦片图 v2版)

第074个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中加载here地图的影像瓦片图 v2软件版本。 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共77行)相关API参考:专栏目标示例效果

C++ 实现游戏(例如MC)键位显示

效果&#xff1a; 是不是有那味儿了&#xff1f; 显示AWSD&#xff0c;空格&#xff0c;Shift和左右键的按键情况以及左右键的CPS。 彩虹色轮廓&#xff0c;黑白填充。具有任务栏图标&#xff0c;可以随时关闭字体是Minecraft AE Pixel&#xff0c;如果你没有装&#xff08;大…

IO流(一):字节流

file是java.io.包下的类&#xff0c;file类的对象&#xff0c;用于代表当前操作系统的文件(可以是文件或者文件夹) file类只能对文件本身进行操作&#xff0c;不能读写文件里面存储的数据 IO流用于读写数据 file代表文本 File 构造方法 File(String pathname)//通过将给定…

网络安全的概述

网络空间的概念 2003年美国提出网络空间的概念&#xff1a;一个由信息基础设施组成的互相依赖的网络。 我国官方文件定义&#xff1a;网络空间为继海&#xff0c;陆&#xff0c;空&#xff0c;天以外的第五大人类活动领域 网络安全发展历史 通信保密阶段 --- 计算机安全阶段…

柔性数组和C语言内存划分

柔性数组和C语言内存划分 1. 柔性数组1.1 柔性数组的特点&#xff1a;1.2 柔性数组的使用1.3 柔性数组的优势 2. 总结C/C中程序内存区域划分 1. 柔性数组 也许你从来没有听说过柔性数组&#xff08;flexible array)这个概念&#xff0c;但是它确实是存在的。 C99 中&#xff…

React16源码: React中的completeUnitOfWork的源码实现

completeUnitOfWork 1 &#xff09;概述 各种不同类型组件的一个更新过程对应的是在执行 performUnitOfWork 里面的 beginWork 阶段它是去向下遍历一棵 fiber 树的一侧的子节点&#xff0c;然后遍历到叶子节点为止&#xff0c;以及 return 自己 child 的这种方式在 performUni…

【小白学机器学习3】关于最简单的线性回归,和用最小二次法评估线性回归效果, 最速下降法求函数的最小值

目录 1 什么是回归分析 1.1 什么是线性回归 1.2非线性回归 2 数据和判断方法 2.1 原始数据 2.2 判断方法&#xff1a;最小二乘法 3 关于线性回归的实测 3.1 用直线模拟 3.2 怎么判断哪个线性模拟拟合更好呢&#xff1f; 3.2.1 判断标准 3.2.2 最小二乘法 3.2.3 高维…

Consul使用详解

简介 Consul是一个由HashiCorp公司开发的开源软件&#xff0c;其发展历程可以概括为以下几个阶段&#xff1a; 初期阶段&#xff08;2014-2015年&#xff09;&#xff1a;Consul最初发布于2014年5月&#xff0c;这个版本是基于Go语言开发的&#xff0c;并提供了诸如服务发现、…

旅游项目day14

其他模块数据初始化 搜索实现 请求一样&#xff0c;但是参数不一样&#xff0c;根据type划分。 后台需要提供一个搜索接口。 请求分发器&#xff1a; 全部搜索 目的地搜索 精确搜索、无高亮展示 攻略搜索 全文搜索、高亮显示、分页 游记搜搜 用户搜索 丝袜哥

01 Redis的特性

1.1 NoSQL NoSQL&#xff08;“non-relational”&#xff0c; “Not Only SQL”&#xff09;&#xff0c;泛指非关系型的数据库。 键值存储数据库 &#xff1a; 就像 Map 一样的 key-value 对。如Redis文档数据库 &#xff1a; NoSQL 与关系型数据的结合&#xff0c;最像关系…