java设计模式——原型模式(Prototype Pattern)

2023-10-27

概述:

       在使用原型模式时,我们需要首先创建一个原型对象,再通过复制这个原型对象来创建更多同类型的对象。需要注意的是通过克隆方法所创建的对象是全新的对象,它们在内存中拥有新的地址,通常对克隆所产生的对象进行修改对原型对象不会造成任何影响,每一个克隆对象都是相互独立的。通过不同的方式修改可以得到一系列相似但不完全相同的对象。

 

定义:

       原型模式(Prototype Pattern):使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式是一种对象创建型模式。

 

结构:

  • Prototype(抽象原型类):它是声明克隆方法的接口,是所有具体原型类的公共父类,可以是抽象类也可以是接口,甚至还可以是具体实现类。
  • ConcretePrototype(具体原型类):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。
  • Client(客户类):让一个原型对象克隆自身从而创建一个新的对象,在客户类中只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。由于客户类针对抽象原型类Prototype编程,因此用户可以根据需要选择具体原型类,系统具有较好的可扩展性,增加或更换具体原型类都很方便。

 

UML图:

场景:比如需要群发邮件的时候,邮件的内容、主题都是一样的,只有收件人不一样而已,所以可以将邮件复制一下,然后修改一下收件人就行了,使用原型模式可以很好的解决这个问题。

 

代码分析:

/**
 * Created by **
 * 具体的原型类:邮件
 */
public class Mail implements Cloneable {
    private String receiver;
    private String title;
    private String context;

    public String getReceiver() {
        return receiver;
    }

    public void setReceiver(String receiver) {
        this.receiver = receiver;
    }

    public String getContext() {
        return context;
    }

    public void setContext(String context) {
        this.context = context;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Mail(){
        LogFactory.log("构造函数被执行了。。。");
    }
    public Mail(AdvTemplate template){
        this.title = template.getAvdtitle();
        this.context = template.getAvdContext();
    }

    @Override
    protected Mail clone() throws CloneNotSupportedException {
        Mail mail = null;
        if (null ==mail){
            mail = (Mail) super.clone();
        }
        return mail;
    }
}

 

/**
 * Created by **
 * 邮件的模板,所有邮件主题、内容是一样的
 */
public class AdvTemplate {
    private String Avdtitle "xxx平台活动";
    private String AvdContext "活动通知,恭喜你获奖1000积分";

    public String getAvdtitle(){
        return this.Avdtitle;
    }
    public String getAvdContext(){
        return this.AvdContext;
    }

}

 

客户端:

Mail mail = new Mail(new AdvTemplate());
mail.setReceiver("小明");
sendMail(mail);
Mail cloneMail = null;
try {
    cloneMail = mail.clone();
    cloneMail.setReceiver("小红");
    sendMail(cloneMail);
    boolean isSame = mail == cloneMail;
    LogFactory.log("is same :" + isSame);
    boolean attributeIsSame = mail.getTitle() == cloneMail.getTitle();
    LogFactory.log("attributeIsSame :" + attributeIsSame);
catch (CloneNotSupportedException e) {
    e.printStackTrace();
}

public void sendMail(Mail mail) {
    LogFactory.log("标题: " + mail.getTitle() + "\t内容:"+mail.getContext()+"\t收件人: " + mail.getReceiver() + "\t....发送成功! ");
}

 

log输出:

08-18 10:57:06.461 24743-24743/? D/test: 标题: xxx平台活动    内容:活动通知,恭喜你获奖1000积分    收件人: 小明    ....发送成功!

08-18 10:57:06.461 24743-24743/? D/test: 标题: xxx平台活动    内容:活动通知,恭喜你获奖1000积分    收件人: 小红    ....发送成功!

08-18 10:57:06.461 24743-24743/? D/test: is same :false

08-18 10:57:06.461 24743-24743/? D/test: attributeIsSame :true

 

      从上述的代码中,没有看到prototype类,因为java里边已经提供了clone方法,我们只要在具体原型类里边实现接口Cloneable就可以了。

Java语言提供的clone()方法 :

      学过Java语言的人都知道,所有的Java类都继承自java.lang.Object。事实上,Object类提供一个clone()方法,可以将一个Java对象复制一份。因此在Java中可以直接使用Object提供的clone()方法来实现对象的克隆,Java语言中的原型模式实现很简单。

       需要注意的是能够实现克隆的Java类必须实现一个标识接口Cloneable,表示这个Java类支持被复制。如果一个类没有实现这个接口但是调用了clone()方法,Java编译器将抛出一个CloneNotSupportedException异常。

 

一般而言,Java语言中的clone()方法满足:

  • 对任何对象x,都有x.clone() != x,即克隆对象与原型对象不是同一个对象;
  • 对任何对象x,都有x.clone().getClass() == x.getClass(),即克隆对象与原型对象的类型一样;
  • 如果对象x的equals()方法定义恰当,那么x.clone().equals(x)应该成立。

为了获取对象的一份拷贝,我们可以直接利用Object类的clone()方法,具体步骤如下:

  • 在派生类中覆盖基类的clone()方法,并声明为public;
  • 在派生类的clone()方法中,调用super.clone();
  • 派生类需实现Cloneable接口。

此时,Object类相当于抽象原型类,所有实现了Cloneable接口的类相当于具体原型类。

 

      另外,log输出里边有 is same :false ,这个说明clone的对象跟之前的对象不再是同一个对象了,但是这个对象的属性是一样的:attributeIsSame :true  ,当然,由于我这里比较的属性是String类型的,所以会容易混淆,下面将在mail类中添加一个对象的引用属性,看看结果怎样:

新添加一个附件类:

/**
 * Created by **
 * 定义邮件的附件
 */
public class Attachment {
    // 附件名
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

 

在客户端添加这部分逻辑:

Attachment attachment = new Attachment();
//将附件添加到周报中
mail.setAttachment(attachment);

boolean attachmentIsSame = mail.getAttachment() == cloneMail.getAttachment();
LogFactory.log("attachmentIsSame :" + attachmentIsSame);

 

log输出:

08-18 11:26:48.471 29343-29343/? D/test: attachmentIsSame :true

 

      从log可以看出,附件的对象是一样的,说明我们复制的mail对象时,里边的属性是同一个对象,也就是在同一个内存区域。因此这里出现 浅克隆 和深克隆这两个概念,上述的代码都是浅克隆,下面介绍一下深克隆的内容:

       在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。浅克隆和深克隆复制对象的区别如下图所示:

                          

 

                                           1-1 浅克隆

 

 

                           

                      

                                          1-2深克隆

       在Java语言中,如果需要实现深克隆,可以通过序列化(Serialization)等方式来实现。序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。下面请看代码实现:

将附件类序列化

/**
 * Created by **
 * 定义邮件的附件
 */
public class Attachment implements Serializable {
    // 附件名
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

 

邮件类序列化,然后通过流来进行克隆(deepClone方法)

/**
 * Created by **
 * 具体的原型类:邮件
 */
public class Mail implements Serializable{
    private String receiver;
    private String title;
    private String context;
    private Attachment attachment;

    public String getReceiver() {
        return receiver;
    }

    public void setReceiver(String receiver) {
        this.receiver = receiver;
    }

    public String getContext() {
        return context;
    }

    public void setContext(String context) {
        this.context = context;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Attachment getAttachment() {
        return attachment;
    }

    public void setAttachment(Attachment attachment) {
        this.attachment = attachment;
    }

    public Mail(){
        LogFactory.log("构造函数被执行了。。。");
    }
    public Mail(AdvTemplate template){
        this.title = template.getAvdtitle();
        this.context = template.getAvdContext();
    }

    /**
     * 深克隆
     @return
     @throws IOException
     * @throws ClassNotFoundException
     */
    public Mail deepClone() throws IOException, ClassNotFoundException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(this);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        Object o = objectInputStream.readObject();
        return (Mail) o;

    }

    /*@Override
    protected Mail clone() throws CloneNotSupportedException {
        Mail mail = null;
        if (null ==mail){
            mail = (Mail) super.clone();
        }
        return mail;
    }*/
}

客户端只需要修改:

cloneMail = mail.deepClone();

 

log输出:

08-18 14:30:49.279 28437-28437/? D/test: 标题: xxx平台活动    内容:活动通知,恭喜你获奖1000积分    收件人: 小明    ....发送成功!

08-18 14:30:49.319 28437-28437/? D/test: 标题: xxx平台活动    内容:活动通知,恭喜你获奖1000积分    收件人: 小红    ....发送成功!

08-18 14:30:49.319 28437-28437/? D/test: is same :false

08-18 14:30:49.319 28437-28437/? D/test: attributeIsSame :false

08-18 14:30:49.319 28437-28437/? D/test: attachmentIsSame :false

 

从log里边可以看到attachmentIsSame :false 这个说明附件类已经被重新克隆了一份,所以两个mail对象包括成员变量全部都复制了一份。

 

优点:

  • 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。
  • 扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统都没有任何影响。
  • 原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。
  • 可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(如恢复到某一历史状态),可辅助实现撤销操作。

 

缺点:

  • 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了“开闭原则”。
  • 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。

 

 

适用场景:

  • 创建新对象成本较大(如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其成员变量稍作修改。
  • 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现。
  • 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。

扩展:

        原型模式与备忘录模式配合使用,效果更佳噢:备忘录模式

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

java设计模式——原型模式(Prototype Pattern) 的相关文章

  • JavaScript 数组迭代返回多个值

    这太简单了 我感到困惑 我有以下内容 var x shrimp var stypes new Array shrimp crabs oysters fin fish crawfish alligator for t in stypes if
  • 如何使用 ROUND HALF UP 进行舍入。我们大多数人在小学时就学过的舍入模式

    如何在javascript中使用ROUND HALF UP进行舍入 我使用的是 Prototype JavaScript 框架版本 1 5 1 rc3 所以我更喜欢使用它 如果可用 如果没有 我也很感激您分享它 任何指导表示赞赏 The 数
  • 系列一、 单例设计模式

    一 单例设计模式 1 1 概述 单例模式 Singleton Pattern 是Java中最简单的设计模式之一 这种类型的设计模式属于创建者模式 它提供了一种创建对象的最佳方式 这种模式涉及到一个单一的类 该类负责创建自己的对象 同时确保只
  • 检查对象是否有用户定义的原型?

    简而言之 我可以检查一个对象是否具有用户定义的原型吗 Example var A function var B function B prototype Pseudocode A hasUserPrototype False B hasUs
  • C 原型范围

    我了解到 声明的类型说明符 参数列表中的标识符 函数原型中的声明 不是函数定义的一部分 标识符具有函数原型 范围 终止于 函数声明符 请参阅下面提到的C 程序 void fn struct st int a a struct st b st
  • 为什么原型未定义

    我知道这个问题已经被问过数百次了 但是 我似乎无法理解这个概念prototype 这是我的示例脚本 var config writable true enumerable true configurable true var defineP
  • C 中“隐式声明函数”警告有何含义?

    正如问题所述 隐式函数声明 警告究竟意味着什么 我们刚刚调高了 gcc 上的警告标志 发现了很多这些警告的实例 我很好奇在修复它们之前这可能会导致什么类型的问题 另外 为什么这是一个警告而不是错误 gcc 如何能够成功链接这个可执行文件 正
  • 如何迭代 Array.prototype 函数

    我想将所有数组函数包装在数组对象中 但在控制台中 gt gt gt Array prototype gt gt gt prototype undefined 但是当我输入时Array prototype在控制台中它显示自动完成中的所有功能
  • RJS:检查现有页面元素?

    我有一个 ID 为 foo 的文本字段 有时存在 有时不存在 如果存在 我想填写一个特定的值 你如何通过使用来做到这一点RJS in 轨道2 2 我尝试了这个 但它不起作用 if page foo page foo value bar en
  • 获取当前function()作用域的所有变量

    我有问题 我想获取当前的函数范围 我有这个示例代码 我工作正常 function nittle var Pen new Dot Generated dynamical through eval for key in window if wi
  • 如何将光标置于文本区域的开头

    我找到了一些关于如何将光标放在文本末尾的文本区域中的资源 但我无法找到一种简单的方法来使其出现在开头 我正在用一些文本预先填充文本区域 只是想让用户更容易 将对文本区域的引用传递给此 JS 函数 function resetCursor t
  • 尝试在 Javascript (ES5) 中实现 OPP 继承的简单方法

    只是出于好奇 我在 Javascript 中玩弄原型继承和 OOP 继承 大多数结果涉及用函数模拟 类 和 扩展 概念 而其他结果则使用原型和构造函数 我写了这段代码 function Warrior weaponName var weap
  • 更改 window.location 原型以禁用某些重定向?

    我正在尝试有选择地禁用window location 使用 Greasemonkey 位于文档开头 我不想完全禁用 javascript 只是禁用一些使用 javascript 完成的重定向 它们看起来像这样 window location
  • 通过 SignalR 在 IE 中缺少原型方法

    我遇到了一个问题 即仅在 IE 中并且仅当数组通过 SignalR 时才处理原型方法消失 在本例中为 Array prototype 方法 我写了一个小 愚蠢但简单的概念验证网络应用程序来演示这个问题 代码全部在下面 请注意 当您单击 更新
  • JavaScript 为什么操作 __proto__ 很慢? [复制]

    这个问题在这里已经有答案了 与此线程相关 JavaScript 修改函数原型的更好方法 https stackoverflow com questions 21788187 javascript better way to modify f
  • Magento,翻译验证错误消息

    我已经成功创建了原型验证的新规则 现在我需要翻译错误消息 位置 Javascript 中的字符串 但是 我只能翻译所有消息 我的新自定义消息似乎无法翻译 我该如何改变这个 也许你需要一个jstranslator xml里面的文件etc fo
  • 为什么 JavaScript 原型属性在新对象上未定义?

    我对 JavaScript 原型概念还很陌生 考虑以下代码 var x function func x prototype log function console log 1 var b new x 据我了解 b log 应该返回 1 因
  • 如何迭代对象原型的属性

    我有一些代码 var obj function functional object obj foo foo obj prototype bar bar for var prop in obj console log prop 让我惊讶的是
  • 为什么间接更改复选框时不会触发复选框上的 onchange

    我使用 Prototype 来监视复选框 因此我可以向它们添加 javascript 检查 当单击复选框所在的 tr 或 td 时 应选中该复选框 当您直接单击复选框时 会触发 onchange 事件 因此您会收到警报 当 javascri
  • 原型组件的 Spring 事件处理

    假设我有两个组件 X 和 Y 其中 X 是单例 而 Y 不是 当我发布XUpdateEvent时 没有问题 我可以捕获该事件 但是 对于 YUpdateEvent 我无法捕获事件 Spring 为每个触发的事件创建新实例 而不是使用已经创建

随机推荐

  • ROS MoveIT1(Noetic)安装总结

    前言 由于MoveIT2的Humble的教程好多用的还是moveit1的环境 所以又装了Ubutun20 04和ROS1 Noetic 2022年12月6日 环境 系统 Ubutun20 04LTS Ros Noetic 虚拟机 VMwar
  • SQL server 实现触发器备份表数据

    在项目里 一个表被增加 需要同步插入的数据 写了一个触发器 需要一个备份表 一个触发器 创建备份表 SELECT INTO PATIENT backup FROM PATIENT 触发器 CREATE TRIGGER dbo Insert
  • 三维图形变换:三维几何变换,投影变换(平行/ 透视 投影)

    通过三维图形变换 可由简单图形得到复杂图形 三维图形变化则分为三维几何变换和投影变换 6 1 三维图形几何变换 三维物体的几何变换是在二维方法基础上增加了对 z 坐标的考虑得到的 有关二维图形几何变换的讨论 基本上都适合三维空间 从应用角度
  • 2023-9-14 数字三角形

    题目链接 数字三角形 include
  • 【AIGC】斯坦福小镇升级版——AI-Town源码解读

    写在前面的话 接上一篇斯坦福小镇升级版 AI Town搭建指南 本本篇将解读 AI Town 使用的技术栈 代码架构 与LLM的交互 以及与斯坦福AI小镇的对比结果 如想直接看结论可跳到文章最后 整体架构 技术栈 AI Town 使用 Ty
  • MATLAB实现列主元高斯消去法

    列主元高斯消去法 function x gauss column A b 输入矩阵A和列向量b 返回解向量x ni nj size b if rank A rank A b 若系数矩阵秩和增广矩阵秩不相等 则无解 fprintf 无解 n
  • 点火开关分为4个档位,分别是off,acc,IG-on,和ST

    off全车除了常火 如应急灯 时钟等的记忆功能 外 均不供电 acc 是附件档 部分车载附属设备供电 如视听系统 仪表灯 灯光等 也就是说 车停在哪里 发动机不转 除了空调不能用外 车内的设备基本都可以用 IG on是汽车点火档 在保证AC
  • 如何用VB实现Modbus串行通讯

    如何用 VB 实现 Modbus 串行通讯 在一些应用中可能需要使用诸如 VB 来进行上位机监控程序的开发 而 Modbus 协议是这类应用中首选的通讯协议 Modbus 协议以其简单易用 在工业领域里已广泛的为其他第三方设备所支持 这里对
  • Warning: JAVA_HOME is not set! Error: Unable to find java executable. Is it in your PATH?

    报错 Warning JAVA HOME is not set Error Unable to find java executable Is it in your PATH 问题描述 启动flume的时候报错 解决方案 1 修改linux
  • PCL系列笔记——(滤波)Filter

    目录 直通滤波 PassThrough filter 体素滤波 VoxelGrid filter 离群点滤波器 StatisticalOutlierRemoval filter 直通滤波 PassThrough filter 这个滤波很直接
  • SpringBoot_第六章(知识点总结)

    目录 目录 1 拦截器 Interceptor 1 1 拦截器代码实现 1 2 拦截器源码分析和流程总结 2 过滤器 Filter 自定义 Servlet 监听器 Listener 3 文件上传 3 1 文件上传代码实现 3 2 文件上传源
  • Aix 压缩、打包、解压、解包 tar zip gz

    tar是打包 zip和gz是压缩 打包 tar cf all tar 解包 tar xvf tar 解压zip文件 jar xvf DB29 5 AIX zip 解压gz文件 usr bin gzip d tar gz
  • highcharts使用韦恩图报错解决Error in mounted hook: “Error: Highcharts error #17:missingModuleFor: venn(详细步骤)

    我由于在vue项目中刚好要使用韦恩图想用highcharts然后按照步骤 1 npm install highcharts save 2 创建组件
  • Mac使用技巧:轻松自定义设置系统键盘

    为你带来Mac OS系统和windows系统如何键盘自定义设置教程 感兴趣可以看看哦 一 mac系统下如何将外接键盘设置成和苹果键盘一样 首先打开mac系统设置里的 键盘 点击 修饰键 选择 usb键盘 然后 option 和 comman
  • 瓦片地图-坐标转换

    先明确三点 1 屏幕坐标是以左上角为原点 而cocos2dx坐标即opengl坐标体系 是以左下角为原点 2 tile地图坐标是以左上角或上方 45 为原点 tile瓦片的默认锚点是左下角 一 地图坐标 Tiled地图一般常见的有3种不同的
  • 【STC15单片机】独立按键显示二进制

    目录 按键选择 按键抖动 独立按键控制8个LED实现二进制显示 显示二进制的程序 单片机型号说明 IAP15F2K61S2 新建工程时单片机型号选择STC15F2K60S2 键盘的分类 键盘分编码键盘和非编码键盘 键盘上闭合件的识别由专用的
  • Python 中的八大关键要素

    阅读本文需要 10 分钟 前言 学习任何一门语言之前 你得先了解它的整体架构 知道它的思想 了解它的关键要素 一门语言学到后来你会发现 就像是在剥茧抽丝一般 越是深入越是发现其奥妙之处 Python 中的八大关键要素 Python 是一种D
  • 云服务器中如何创建共享文件夹,云服务器中如何创建共享文件夹

    云服务器中如何创建共享文件夹 内容精选 换一换 当您有如下需求时 可以考虑使用文件注入功能将文件注入到弹性云服务器 需要通过脚本简化弹性云服务器配置通过脚本初始化系统已有脚本 在创建弹性云服务器的时候一并上传到服务器其他可以使用脚本完成的事
  • css-滚动条样式设置

    滚动条产生原因 给能设置宽高的元素添加 overflow scroll 样式 会让该元素区域产生滚动条 滚动条默认样式 以下行文案例皆是在 Edge 浏览器环境下测试 设置滚动条样式 通过设置 webkit scrollbar 伪元素影响滚
  • java设计模式——原型模式(Prototype Pattern)

    概述 在使用原型模式时 我们需要首先创建一个原型对象 再通过复制这个原型对象来创建更多同类型的对象 需要注意的是通过克隆方法所创建的对象是全新的对象 它们在内存中拥有新的地址 通常对克隆所产生的对象进行修改对原型对象不会造成任何影响 每一个