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

发布时间 2023-04-24 10:33:27作者: 霸道流氓

场景

设计模式-原型模式-浅克隆和深克隆在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+"123@123.com");
            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+"123@123.com");
            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());
        //[张三]
    }
}

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

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