设计模式之禅-原型模式

银行发广告信,为了提供个性化服务,发过去的邮件需要带上个人信息,如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<Max_COUNT){

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

            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<maxLength;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<MAX_COUNT){

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

            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 关键字。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容