首页 > 编程语言 >设计模式-原型模式-Java中使用示例-节日发送邮件活动

设计模式-原型模式-Java中使用示例-节日发送邮件活动

时间:2023-04-24 10:35:28浏览次数:42  
标签:Java String 示例 对象 clone private 拷贝 设计模式 public

场景

设计模式-原型模式-浅克隆和深克隆在Java中的使用示例:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/127576328

上面初步记录了原型模式的使用示例,下面再记录一个银行节假日或者搞活动

时发送邮件的例子。

原型模式

原型模式(Prototype Pattern)的简单程度仅次于单例模式和迭代器模式。正是由于简单,使用的场景才非常地多,

其定义如下:

Specify the kinds of objects to create using a prototypical instance,and create new objects by copying this prototype.

(用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。)

简单,太简单了!原型模式的核心是一个clone方法,通过该方法进行对象的拷贝,Java提供了一个Cloneable接口来标示

这个对象是可拷贝的,为什么说是“标示”呢?翻开JDK的帮助看看Cloneable是一个方法都没有的,这个接口只是一个标记作用,

在JVM中具有这个标记的对象才有可能被拷贝。那怎么才能从“有可能被拷贝”转换为“可以被拷贝”呢?

方法是覆盖clone()方法,是的,你没有看错是重写clone()方法。

原型模式的优点

性能优良

原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,

原型模式可以更好地体现其优点。

逃避构造函数的约束

这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的**。优点就是减少了约束,

缺点也是减少了约束,需要大家在实际应用时考虑。

原型模式的使用场景

资源优化场景

类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。

性能和安全要求的场景

通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。

一个对象多个修改者的场景

一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。

在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,

然后由工厂方法提供给调用者。

原型模式的注意事项

构造函数不会被执行

一个实现了Cloneable并重写了clone方法的类A,有一个无参构造或有参构造B,通过new关键字产生了一个对象S,

再然后通过S.clone()方式产生了一个新的对象T,那么在对象拷贝时构造函数B是不会被执行的。具体可以参考示例代码。

浅拷贝和深拷贝

注意:深拷贝和浅拷贝建议不要混合使用,特别是在涉及类的继承时,父类有多个引用的情况就非常复杂,

建议的方案是深拷贝和浅拷贝分开实现。

关于浅拷贝和深拷贝可以参考示例代码。

clone与final两个冤家

对象的clone与对象内的final关键字是有冲突的,你要使用clone方法,在类的成员变量上就不要增加final关键字。

注:

博客:
https://blog.csdn.net/badao_liumang_qizhi

实现

1、某银行节日发送抽奖活动通知邮件。

新建广告信模板代码

@Data
public class AdvTemplate {
    //广告信名称
    private String advSubject = "xx银行抽奖活动";
    //广告信内容
    private String advContext = "抽奖活动通知:......";
}

2、新建邮件类代码

//邮件类代码
@Data
public class Mail {
    //收件人
    private String receiver;
    //邮件名称
    private String subject;
    //称谓
    private String appellation;
    //内容
    private String context;
    //邮件底部,一般是加上"xxx版权所有"等信息
    private String tail;
    public Mail(AdvTemplate advTemplate){
        this.context = advTemplate.getAdvContext();
        this.subject = advTemplate.getAdvSubject();
    }
}

3、场景类

//场景类
public class Client {
    //发送账单的数量
    private static int MAX_COUNT = 6;

    public static void main(String[] args) {
        //模拟发送邮件
        int i =0;
        //把模板定义出来,这个一般从数据库中获取
        Mail mail = new Mail(new AdvTemplate());
        mail.setTail("badaoxxx版权所有");
        while (i<MAX_COUNT){
            //每封邮件不同的地方
            mail.setAppellation(i+"先生/女士");
            mail.setReceiver(i+"[email protected]");
            sendMail(mail);
            i++;
        }
    }

    public static void sendMail(Mail mail){
        System.out.println("标题:"+mail.getSubject()+"\t收件人:"+mail.getReceiver()+"\t...发送成功!");
    }
}

问题:

发送的单线程的,需要把sendMail修改为多线程,也会有问题,产生第一封邮件对象,放到线程一中运行,还没有发送出去;

线程2也启动了,直接就把邮件对象的的收件人和称谓给改了,线程不安全了,当然这有其它N多中解决方法,

其中一种就是使用一种新型模式来解决这个问题:通过对象的复制功能来解决这个问题。

4、修改后的邮件类

//修改后的邮件类
@Data
public class MailExtend implements Cloneable{
    //收件人
    private String receiver;
    //邮件名称
    private String subject;
    //称谓
    private String appellation;
    //内容
    private String context;
    //邮件底部,一般是加上"xxx版权所有"等信息
    private String tail;
    public MailExtend(AdvTemplate advTemplate){
        this.context = advTemplate.getAdvContext();
        this.subject = advTemplate.getAdvSubject();
    }

    @Override
    public MailExtend clone() throws CloneNotSupportedException {
        MailExtend mailExtend = null;
        mailExtend = (MailExtend) super.clone();
        return mailExtend;
    }
}

5、修改后的场景类

//修改后的场景类
public class ClientExtend {
    //发送账单的数量
    private static int MAX_COUNT = 6;

    public static void main(String[] args) throws CloneNotSupportedException {
        //模拟发送邮件
        int i =0;
        //把模板定义出来,这个一般从数据库中获取
        MailExtend mail = new MailExtend(new AdvTemplate());
        mail.setTail("badaoxxx版权所有");
        while (i<MAX_COUNT){
            //每封邮件不同的地方
            MailExtend cloneMail = mail.clone();
            mail.setAppellation(i+"先生/女士");
            mail.setReceiver(i+"[email protected]");
            sendMail(cloneMail);
            i++;
        }
    }

    public static void sendMail(MailExtend mail){
        System.out.println("标题:"+mail.getSubject()+"\t收件人:"+mail.getReceiver()+"\t...发送成功!");
    }
}

运行结果不变,而且sendMail即使是多线程也没有关系。

Client类中的mail.clone(),把对象复制一份,产生一个新的对象,和原有对象一样,然后再修改细节的数据,

如设置称谓、收件人地址等。这种不通过new 关键字来产生一个对象,而是通过对象复制来实现的模式就叫做原型模式。

6、验证clone之后的构造函数不会被执行

//构造函数不会被执行
public class Thing implements Cloneable{
    public Thing(){
        System.out.println("构造函数被执行了...");
    }

    @Override
    public Thing clone() throws CloneNotSupportedException {
        Thing thing = null;
        thing = (Thing)super.clone();
        return thing;
    }
}

再写一个Client 进行对象的拷贝

public class TestClient {
    public static void main(String[] args) throws CloneNotSupportedException {
        Thing thing = new Thing();
        Thing cloneThing = thing.clone();
    }
}

从运行结果中看到,对象拷贝时构造函数没有被执行

Object类的clone方法的原理是从内存中以二进制流的方式进行拷贝,重新分配一个内存块。

7、浅克隆与深克隆

看一个浅克隆的例子

public class QianClone implements Cloneable{
    //定义一个私有变量
    private ArrayList<String> arrayList = new ArrayList<>();
    @Override
    public QianClone clone() throws CloneNotSupportedException {
        QianClone thing = null;
        thing = (QianClone)super.clone();
        return thing;
    }
    public void setValue(String value){
        this.arrayList.add(value);
    }
    public ArrayList<String> getValue(){
        return this.arrayList;
    }
}

调用浅克隆的场景类

public class QianCloneClient {
    public static void main(String[] args) throws CloneNotSupportedException {
        QianClone qianClone = new QianClone();
        qianClone.setValue("张三");
        QianClone qianCloneClone = qianClone.clone();
        qianCloneClone.setValue("李四");
        System.out.println(qianClone.getValue());
        //[张三, 李四]
    }
}

增加一个私有变量arrayList,然后运行结果不光有张三,还有李四。

是因为Object类的clone方法只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址,

这种拷贝就是浅拷贝。两个对象共享了一个私有变量,你改大家都能改。那么为什么在Mail那个类中就可以使用String类型,

而不会产生由浅拷贝带来的问题。因为内部的数组和对象才不拷贝,其它的原始类型int、long、cha等以及String都会被拷贝。

深拷贝的例子

//深拷贝
public class ShenClone implements Cloneable{
    //定义一个私有变量
    private ArrayList<String> arrayList = new ArrayList<>();
    @Override
    public ShenClone clone() throws CloneNotSupportedException {
        ShenClone thing = null;
        thing = (ShenClone)super.clone();
        thing.arrayList = (ArrayList<String>) this.arrayList.clone();
        return thing;
    }
    public void setValue(String value){
        this.arrayList.add(value);
    }
    public ArrayList<String> getValue(){
        return this.arrayList;
    }
}

深拷贝场景类

public class ShenCloneClient {
    public static void main(String[] args) throws CloneNotSupportedException {
        ShenClone shenClone = new ShenClone();
        shenClone.setValue("张三");
        ShenClone shenClone1 = shenClone.clone();
        shenClone1.setValue("李四");
        System.out.println(shenClone.getValue());
        //[张三]
    }
}

对私有的类变量进行独立的拷贝。

该方法实现了完全的拷贝,两个对象之间没有任何的瓜葛了,不互相影响,这种就是深拷贝。

 

标签:Java,String,示例,对象,clone,private,拷贝,设计模式,public
From: https://www.cnblogs.com/badaoliumangqizhi/p/17348673.html

相关文章

  • 五分钟理解Java算法的时间复杂度
    关注我了解更多Java技术知识,带你一路“狂飙”到底!上岸大厂不是梦!前言时间复杂度主要是为了反映函数的执行时间随着输入规模增长而变化的规律,在一定程度上可以体现程序的执行效率和算法的优劣。作为程序员,掌握基本的算法时间复杂度的计算是很有必要的。时间复杂度介绍理论上,执......
  • Java-Day-14( 枚举 + 注解 + 自设头文件 )
    Java-Day-14枚举(enumeration,enum)若是创建春夏秋冬四季的信息,如果按传统方法创建,无法固定信息,可以随时调改,所以要用枚举,做到只读且不能改枚举一组常量的集合——属于一种特殊的类,里面只包含一组有限的特定的对象实现方式自定义类实现枚举构造器私有化......
  • Java 常见报错解决方案
    1.常见的java异常分类Throwable类有两个直接子类:Exception:出现的问题是可以被捕获的Check异常:派生自Exception的异常类,必须被捕获或再次声明抛出Runtime异常:派生自RuntimeException的异常类。使用throw语句可以随时抛出这种异常对象thrownewArithmeticException(…);......
  • 力扣977(Java)-有序数组的平方(简单)
    题目:给你一个按非递减顺序排序的整数数组nums,返回每个数字的平方组成的新数组,要求也按非递减顺序排序。 示例1:输入:nums=[-4,-1,0,3,10]输出:[0,1,9,16,100]解释:平方后,数组变为[16,1,0,9,100]排序后,数组变为[0,1,9,16,100]示例2:输入:nums=[-7,-3,2,3,11]输出:[4,......
  • IDEA中JavaDocs路径是红色的
    转载链接:https://blog.csdn.net/Chia_Hung_Yeh/article/details/102936633ProjectSettings-->Libraries-->Sources、JavaDocs路径出现红色字体ClassesClasses中的jar,是程序在运行项目的时候使用的,因为这个是直接编译好的class文件,可以直接被虚拟机运行的。SourcesSource......
  • golang中sync.Pool的使用示例
    先上代码:packagemainimport( "fmt" "sync")varpoolsync.Pooltypepersonstruct{ Namestring Ageint}funcinit(){ pool=sync.Pool{New:func()any{ returnnew(person) }}}funcmain(){ p:=pool.Get().(*person) p......
  • java -- 枚举和反射
    枚举枚举概述枚举是JDK1.5新增的引用数据类型,和类,接口是一个级别的,定义枚举的关键字为enum。java.lang.Enum类,是所有枚举的父类。枚举的本质就是一个类的多个对象。枚举的定义格式:publicenmu枚举名{}枚举常量定义:枚举中的常量名字大写,多个常量之间逗号分开,最后一个常......
  • Javascript数据类型
    值类型和引用类型原始类型(alias:值类型,基础类型)primitive:stringnumberbooleannullundefinedsymbol引用类型:Object其他内置Object派送类型ArrayFunctionMapSetWeakMapWeakSetRegExpNaN:特殊的Number类型,IsNaN()判断一个值是否为NaN引用类型可以有......
  • java调用GDAL,接口运行一次出现A fatal error has been detected by the Java Runtime
    参考文章:https://www.jianshu.com/p/4bffe29e3a02问题描述:通过调用GDAL写的SpringBoot接口,第一次访问成功,第二次报错,显示报错的位置为gdal库。尝试了很多方法https://www.cnblogs.com/jokingremarks/p/15132599.html#!comments仍然不成功,感觉应该是第二次运行接口时,进行垃圾回......
  • Google Guava常用的代码示例
    GoogleGuava谷歌出品的,非常实用。包含集合、并发、I/O、散列、缓存、字符串等。依赖:<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>18.0</version></dependency>JoinerJoiner可以连接字符串。常用方法:Joine......