设计模式之禅-原型模式

银行发广告信,为了提供个性化服务,发过去的邮件需要带上个人信息,如XX先生/小姐,又或者是电子账单,这就需要一个模板,再由具体数据填充成一份完整的邮件。


AdvTemplate 是广告信的模板,一般都是从数据库取出,生成一个 BO 或者是 DTO,我们这里使用一个静态的值来做代表;

public class AdvTemplate {

    //广告信名称

    private String advSubject ="XX银行国庆信用卡抽奖活动";

    //广告信内容

    private String advContext = "国庆抽奖活动通知:只要刷卡就送你1百万!....";

    //取得广告信的名称

    public String getAdvSubject(){

        return this.advSubject;

    }

    //取得广告信的内容

    public String getAdvContext(){

        return this.advContext;

    }

}

Mail 类是一封邮件类,发送机发送的就是这个类。

public class Mail {

    //收件人

    private String receiver;

    //邮件名称

    private String subject;

    //称谓

    private String appellation;

    //邮件内容

    private String contxt;

    //邮件的尾部,一般都是加上“XXX版权所有”等信息

    private String tail;

    //构造函数

    public Mail(AdvTemplate advTemplate){

        this.contxt = advTemplate.getAdvContext();

        this.subject = advTemplate.getAdvSubject();

    }

    //以下为getter/setter方法

    public String getReceiver() {

        return receiver;

    }

    public void setReceiver(String receiver) {

        this.receiver = receiver;

    }

    public String getSubject() {

        return subject;

    }

    public void setSubject(String subject) {

        this.subject = subject;

    }

    public String getAppellation() {

        return appellation;

    }

    public void setAppellation(String appellation) {

        this.appellation = appellation;

    }

    public String getContxt() {

        return contxt;

    }

    public void setContxt(String contxt) {

        this.contxt = contxt;

    }

    public String getTail() {

        return tail;

    }

    public void setTail(String tail) {

        this.tail = tail;

    }

}

业务场景类

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("XX银行版权所有");

        while(i

            //以下是每封邮件不同的地方

            mail.setAppellation(getRandString(5)+" 先生(女士)");

            mail.setReceiver(getRandString(5) + "@" + getRandString(8)+".com");

            //然后发送邮件

            sendMail(mail);

            i++;

        }

    }

    //发送邮件

    public static void sendMail(Mail mail){

        System.out.println("标题:"+mail.getSubject() + "\t收件人:"+mail.getReceiver()+"\t....发送成功!");

    }

    //获得指定长度的随机字符串

    public static String getRandString(int maxLength){

        String source ="abcdefghijklmnopqrskuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

        StringBuffer sb = new StringBuffer();

        Random rand = new Random();

       for(int i=0;i

            sb.append(source.charAt(rand.nextInt(source.length())));

        }

        return sb.toString();

    }

}

现在发送邮件是单线程的,假设每封邮件0.02秒,600 万封邮件需要33小时,今天发送不完毕,明天的账单又产生了,积累积累,激起甲方人员一堆抱怨。

把 sendMail 修改为多线程,但是你只把 sendMail 修改为多线程还是有问题的呀,你看哦,产生第一封邮件对象,放到线程 1 中运行,还没有发送出去;线程 2 呢也也启动了,直接就把邮件对象 mail的收件人地址和称谓修改掉了,线程不安全了,好了,说到这里,你会说这有 N 多种解决办法,我们不多说,我们今天就说一种,使用原型模式来解决这个问题,使用对象的拷贝功能来解决这个问题,类图稍作修改:


增加了一个 Cloneable 接口, Mail 实现了这个接口,在 Mail 类中重写了 clone()方法,我们来看 Mail类的改变:

public class Mail implements Cloneable{

    //收件人

    private String receiver;

    //邮件名称

    private String subject;

    //称谓

    private String appellation;

    //邮件内容

    private String contxt;

    //邮件的尾部,一般都是加上“XXX版权所有”等信息

    private String tail;

    //构造函数

    public Mail(AdvTemplate advTemplate){

        this.contxt = advTemplate.getAdvContext();

        this.subject = advTemplate.getAdvSubject();

    }

    @Override

    public Mail clone(){

        Mail mail =null;

        try {

            mail = (Mail)super.clone();

        } catch (CloneNotSupportedException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        }

        return mail;

    }

    //以下为getter/setter方法

}

看 Client 类的改变:

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("XX银行版权所有");

        while(i

            //以下是每封邮件不同的地方

            Mail cloneMail = mail.clone();

            cloneMail.setAppellation(getRandString(5)+" 先生(女士)");

            cloneMail.setReceiver(getRandString(5) + "@" +getRandString(8)+".com");

            //然后发送邮件

            sendMail(cloneMail);

            i++;

        }

    }

}

运行结果不变,一样完成了电子广告信的发送功能,而且 sendMail 即使是多线程也没有关系,看到mail.clone()这个方法了吗?把对象拷贝一份,产生一个新的对象,和原有对象一样,然后再修改细节的数据,如设置称谓,设置收件人地址等等。这种不通过 new 关键字来产生一个对象,而是通过对象拷贝来实现的模式就叫做原型模式,其通用类图如下

这个模式的核心是一个 clone 方法,通过这个方法进行对象的拷贝,Java 提供了一个 Cloneable 接口来标示这个对象是可拷贝的,为什么说是“标示”呢?翻开 JDK 的帮助看看 Cloneable 是一个方法都没有的,这个接口只是一个标记作用,在 JVM 中具有这个标记的对象才有可能被拷贝,那怎么才能从“有可能被拷贝”转换为“可以被拷贝”呢?方法是覆盖 clone()方法。

在 clone()方法上增加了一个注解@Override,没有继承一个类为什么可以重写呢?在 Java 中所有类的老祖宗是谁?Object 类,每个类默认都是继承了这个类,所以这个用上重写是非常正确的。

在 Java 中使用原型模式也就是 clone 方法还是有一些注意事项的

1.对象拷贝时,类的构造函数是不会被执行的。对象拷贝时确实构造函数没有被执行,这个从原理来讲也是可以讲得通的,Object 类的 clone 方法的原理是从内存中(具体的说就是堆内存)以二进制流的方式进行拷贝,重新分配一个内存块,那构造函数没有被执行也是非常正常的了。

2.浅拷贝和深拷贝问题。

浅拷贝

public class Thing implements Cloneable{

    //定义一个私有变量

    private ArrayListarrayList = new ArrayList();

    @Override

    public Thing clone(){ 

        Thing thing=null;

        try { 

            thing = (Thing)super.clone(); 

        } catch (CloneNotSupportedException e) { 

            e.printStackTrace();

         }

         return thing; 

    }

    //设置HashMap的值

    public void setValue(String value){ this.arrayList.add(value); }

    //取得arrayList的值

    public ArrayList getValue(){return this.arrayList;}

}

在 Thing 类中增加一个私有变量 arrayLis,类型为 ArrayList,然后通过 setValue 和 getValue 分别进行设置和取值,我们来看场景类:

public class Client {

    public static void main(String[] args) {

        //产生一个对象

        Thing thing = new Thing();

        //设置一个值

        thing.setValue("张三");

        //拷贝一个对象

        Thing cloneThing = thing.clone();

        cloneThing.setValue("李四");

        System.out.println(thing.getValue());

    }

}

运行结果:

[张三, 李四] 

怎么会有李四呢?是因为 Java 做了一个偷懒的拷贝动作,Object 类提供的方法 clone 只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址,这种拷贝就叫做浅拷贝,两个对象共享了一个私有变量,你改我改大家都能改,是一个种非常不安全的方式。

深拷贝:

public class Thing implements Cloneable{

    //定义一个私有变量

    private ArrayListarrayList = new ArrayList();

    @Overridepublic Thing clone(){ 

        Thing thing=null;

         try { 

                thing = (Thing)super.clone();

                thing.arrayList = (ArrayList)this.arrayList.clone();

         } catch (CloneNotSupportedException e) {

                e.printStackTrace();

        }

        return thing;

    }

}

运行结果如下:

[张三]

这个实现了完全的拷贝,两个对象之间没有任何的瓜葛了

3.Clone 与 final 两对冤家,对象的 clone 与对象内的 final 属性是由冲突的。

public class Thing implements Cloneable{

    //定义一个私有变量

    private final ArrayListarrayList = new ArrayList();

    @Override

    public Thing clone(){ 

        Thing thing=null; 

        try {

            thing = (Thing)super.clone(); 

            this.arrayList = (ArrayList)this.arrayList.clone();

        } catch (CloneNotSupportedException e) {

            e.printStackTrace();

        }

        return thing;

    }

}

ArrayListarrayList 增加了一个 final 关键字,你要使用 clone 方法就在类的成员变量上不要增加 final 关键字。

你可能感兴趣的:(设计模式之禅-原型模式)