设计模式之禅8

来源:互联网 发布:linux curl post body 编辑:程序博客网 时间:2024/06/06 17:17

设计模式之禅8

真刀实枪之原型模式

  • 先发个个性化电子账单来引出今天的主角
    • 个性化服务
      • 一般银行都会要求个性化服务,添加一些个人信息在提示信息之前。
    • 递送的成功率
      • 邮件的递送成功率有一定的要求,由于大批的发送邮件,会被接收的邮件服务器认为是垃圾邮件,因此要在头信息增加一些伪数据,以规避被反垃圾邮件引擎认为是垃圾邮件
  • 电子账单系统
    1. 账单分析
    2. 账单生成器
    3. 广告信息管理
    4. 发送队列管理
    5. 发送机
    6. 退信处理
    7. 报表管理
  • 类图

    • AdvTemplate是广告模板
    • Mail是一封邮件类
    • 代码

      • AdvTemplate

        package com.peng.mail;/** * @author kungfu~peng * @data 2017年11月19日 * @description */public class AdvTemplate {    // 广告信名称    private String advSubject = "kungfu银行抽奖活动";    // 广告信内容    private String advContext = "抽奖活动,只要抽中就给你100W";    // 获得广告的名称    public String getAdvSubject() {        return advSubject;    }    // 获得广告的内容    public String getAdvContext() {        return advContext;    }}
      • Mail

        package com.peng.mail;/** * @author kungfu~peng * @data 2017年11月19日 * @description */public class Mail {    // 收件人    private String receiver;    // 邮件名称    private String subject;    // 称谓    private String application;    // 邮件内容    private String context;    // 邮件尾部    private String tail;    // 构造函数    public Mail(AdvTemplate advTemplate) {        this.subject = advTemplate.getAdvSubject();        this.context = advTemplate.getAdvContext();    }    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 getApplication() {        return application;    }    public void setApplication(String application) {        this.application = application;    }    public String getContext() {        return context;    }    public void setContext(String context) {        this.context = context;    }    public String getTail() {        return tail;    }    public void setTail(String tail) {        this.tail = tail;    }}
      • Client

        package com.peng.mail;import java.util.Random;/** * @author kungfu~peng * @data 2017年11月19日 * @description */public class Client {    // 发送账单的数量    private static int MAX_COUNT = 10;    public static void main(String[] args) {        // 模拟发送邮        int i = 0;        // 把模板定义出来        Mail mail = new Mail(new AdvTemplate());        mail.setTail("版权归kungfu银行所有。");        while (i < MAX_COUNT) {            // 发往的地方            mail.setApplication(getRandString(5) + "女士/先生");            // 发送邮件            mail.setReceiver(getRandString(5) + "@" + getRandString(8) + ".com");            sendMail(mail);            i++;        }    }    // 函数--发送邮件    public static void sendMail(Mail mail) {        System.out.println("主题:" + mail.getSubject() + ",收件人:"                + mail.getReceiver() + ",发送成功!");    }    // 获得指定长度的随机字符串    public static String getRandString(int maxLength) {        String source = "abcdefghijklmnopqrstABCDEFGHIJKLMNOPQRST";        Random random = new Random();        StringBuffer sb = new StringBuffer();        for (int i = 0; i < maxLength; i++) {            sb.append(source.charAt(random.nextInt(source.length())));        }        return sb.toString();    }}
    • 这个程序有问题吗?单线程没啥大问题,可能时间长点,但是试过多线程没!!出现了线程的混乱,出现了重复发送。既然出现了问题,那么先来改改我们的类图吧!

      • 代码

        • Mail

          package com.peng.mail;/** * @author kungfu~peng * @data 2017年11月19日 * @description */public class Mail implements Cloneable {    // 收件人    private String receiver;    // 邮件名称    private String subject;    // 称谓    private String application;    // 邮件内容    private String context;    // 邮件尾部    private String tail;    // 构造函数    public Mail(AdvTemplate advTemplate) {        this.subject = advTemplate.getAdvSubject();        this.context = advTemplate.getAdvContext();    }    // clone方法    @Override    protected Object clone() throws CloneNotSupportedException {        Mail mail = null;        try {            mail = (Mail) super.clone();        } catch (CloneNotSupportedException e) {            e.printStackTrace();        }        return mail;    }    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 getApplication() {        return application;    }    public void setApplication(String application) {        this.application = application;    }    public String getContext() {        return context;    }    public void setContext(String context) {        this.context = context;    }    public String getTail() {        return tail;    }    public void setTail(String tail) {        this.tail = tail;    }}
        • 修改后的Client

          package com.peng.mail;import java.util.Random;/** * @author kungfu~peng * @data 2017年11月19日 * @description */public class Client {    // 发送账单的数量    private static int MAX_COUNT = 10;    public static void main(String[] args) throws CloneNotSupportedException {        new Thread() {            public void run() {                // 模拟发送邮                int i = 0;                // 把模板定义出来                Mail mail = new Mail(new AdvTemplate());                mail.setTail("版权归kungfu银行所有。");                while (i < MAX_COUNT) {                    Mail cloneObj = null;                    try {                        cloneObj = mail.clone();                    } catch (CloneNotSupportedException e) {                        // TODO Auto-generated catch block                        e.printStackTrace();                    }                    // 发往的地方                    cloneObj.setApplication(getRandString(5) + "女士/先生");                    // 发送邮件                    cloneObj.setReceiver(getRandString(5) + "@"                            + getRandString(8) + ".com");                    sendMail(cloneObj);                    i++;                }            };        }.start();        new Thread() {            public void run() {                // 模拟发送邮                int i = 0;                // 把模板定义出来                Mail mail = new Mail(new AdvTemplate());                mail.setTail("版权归kungfu银行所有。");                while (i < MAX_COUNT) {                    Mail cloneObj = null;                    try {                        cloneObj = mail.clone();                    } catch (CloneNotSupportedException e) {                        // TODO Auto-generated catch block                        e.printStackTrace();                    }                    // 发往的地方                    cloneObj.setApplication(getRandString(5) + "女士/先生");                    // 发送邮件                    cloneObj.setReceiver(getRandString(5) + "@"                            + getRandString(8) + ".com");                    sendMail(cloneObj);                    i++;                }            };        }.start();        new Thread() {            public void run() {                // 模拟发送邮                int i = 0;                // 把模板定义出来                Mail mail = new Mail(new AdvTemplate());                mail.setTail("版权归kungfu银行所有。");                while (i < MAX_COUNT) {                    Mail cloneObj = null;                    try {                        cloneObj = mail.clone();                    } catch (CloneNotSupportedException e) {                        // TODO Auto-generated catch block                        e.printStackTrace();                    }                    // 发往的地方                    cloneObj.setApplication(getRandString(5) + "女士/先生");                    // 发送邮件                    cloneObj.setReceiver(getRandString(5) + "@"                            + getRandString(8) + ".com");                    sendMail(cloneObj);                    i++;                }            };        }.start();    }    // 函数--发送邮件    public static void sendMail(Mail mail) {        System.out.println("主题:" + mail.getSubject() + ",收件人:"                + mail.getReceiver() + ",发送成功!");    }    // 获得指定长度的随机字符串    public static String getRandString(int maxLength) {        String source = "abcdefghijklmnopqrstABCDEFGHIJKLMNOPQRST";        Random random = new Random();        StringBuffer sb = new StringBuffer();        for (int i = 0; i < maxLength; i++) {            sb.append(source.charAt(random.nextInt(source.length())));        }        return sb.toString();    }}
        • 运行结果(随机数据各有千秋)

          主题:kungfu银行抽奖活动,收件人:aHiKE@dMcLCdAk.com,发送成功!主题:kungfu银行抽奖活动,收件人:DqTKg@fCNnqLJm.com,发送成功!主题:kungfu银行抽奖活动,收件人:jreLC@BBPnJOlD.com,发送成功!主题:kungfu银行抽奖活动,收件人:tkfgH@hScMgggf.com,发送成功!主题:kungfu银行抽奖活动,收件人:TKbpF@HcbObcBh.com,发送成功!主题:kungfu银行抽奖活动,收件人:MbgIb@DAtBSSPc.com,发送成功!主题:kungfu银行抽奖活动,收件人:mPGGE@TFJCPONn.com,发送成功!主题:kungfu银行抽奖活动,收件人:TcHSS@cHQLcSTF.com,发送成功!主题:kungfu银行抽奖活动,收件人:IrDMO@bPceTKBc.com,发送成功!主题:kungfu银行抽奖活动,收件人:HCiAg@krTkPHRM.com,发送成功!主题:kungfu银行抽奖活动,收件人:psJHk@kKQkddCP.com,发送成功!主题:kungfu银行抽奖活动,收件人:cMlrQ@sPmRLjmP.com,发送成功!主题:kungfu银行抽奖活动,收件人:HjatC@HgCrjjtn.com,发送成功!主题:kungfu银行抽奖活动,收件人:FQnJG@ARPRcLAj.com,发送成功!主题:kungfu银行抽奖活动,收件人:QBmEN@BDosMQpS.com,发送成功!主题:kungfu银行抽奖活动,收件人:PsDmL@CnOmCCEl.com,发送成功!主题:kungfu银行抽奖活动,收件人:iMNlo@SGqkStEK.com,发送成功!主题:kungfu银行抽奖活动,收件人:GBbHP@sdrjeOjE.com,发送成功!主题:kungfu银行抽奖活动,收件人:GPsSf@QNpaQFKD.com,发送成功!主题:kungfu银行抽奖活动,收件人:HbHKl@IjrtpNBR.com,发送成功!主题:kungfu银行抽奖活动,收件人:THhCQ@GmfGTfqT.com,发送成功!主题:kungfu银行抽奖活动,收件人:GnbIs@FEnFSrTL.com,发送成功!主题:kungfu银行抽奖活动,收件人:EpggK@boqLdbJf.com,发送成功!主题:kungfu银行抽奖活动,收件人:Jblbp@GKnCDRsD.com,发送成功!主题:kungfu银行抽奖活动,收件人:fiCOd@sKqinsQT.com,发送成功!主题:kungfu银行抽奖活动,收件人:pBEmd@GJJNeIoH.com,发送成功!主题:kungfu银行抽奖活动,收件人:Ftdmp@qropOmna.com,发送成功!主题:kungfu银行抽奖活动,收件人:tSnBP@mlNGChpn.com,发送成功!主题:kungfu银行抽奖活动,收件人:jgbja@POTIiPBG.com,发送成功!主题:kungfu银行抽奖活动,收件人:nahpO@kLqHMaDL.com,发送成功!
        • 上边代码中,把对象复制一份,产生一个新的对象,和原有对象一致,然后修改细节数据。这种不通过new关键字来产生的一个对象,而是通过对象的复制来实现的模式成为原型模式

原型模式的定义

  • Prototype Pattern
  • Specify the kinds of objects to create using a prototypical instance , and create new objects by copying this prototype.(用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象)
  • 通用类图
    • 类图很简单,核心就是一个clone方法,看看Cloneable接口,一个方法也没有,那么clone方法从哪里来?别忘了超类Object,其中有方法为clone
      • Object类中的clone方法原码: protected native Object clone() throws CloneNotSupportedException;
  • 通用源码(核心代码)

    • Prototype

      package com.peng.yuanxing;/** * @author kungfu~peng * @data 2017年11月19日 * @description */public class Prototype implements Cloneable {    // 覆写父类的clone方法    @Override    protected Prototype clone() throws CloneNotSupportedException {        Prototype prototype = null;        try {            prototype = (Prototype) super.clone();        } catch (CloneNotSupportedException e) {            e.printStackTrace();        }        return prototype;    }}

原型模式的应用

  • 原型模式的优点
    • 性能优良:内存二进制流的拷贝
    • 逃避构造函数的约束:直接在内存中执行,构造函数不会执行
  • 使用场景
    • 资源优化:数据、硬件资源
    • 性能和安全的场景
    • 一个对象多个修改者的场景
  • 在实际项目中,原型模式很少单独出现,一般是和工厂模式一起出现,通过clone方法创建一个对象,然后由工厂方法提供给调用者。

原型模式的注意事项

  • 构造方法不被执行(如下:)

    package com.peng.yuanxing;/** * @author kungfu~peng * @data 2017年11月19日 * @description */public class Thing implements Cloneable {    public Thing() {        super();        System.out.println("我是构造函数!");    }    @Override    protected Thing clone() throws CloneNotSupportedException {        Thing thing = null;        try {            thing = (Thing) super.clone();        } catch (CloneNotSupportedException e) {            e.printStackTrace();        }        return thing;    }    public static void main(String[] args) throws CloneNotSupportedException {        Thing t0 = new Thing();        Thing t1 = t0.clone();        Thing t2 = t0.clone();        Thing t3 = t0.clone();    }}
    • 执行结果

      我是构造函数!
    • 解释:Object的clone方法的原理是从内存中(堆内存)以二进制流的方式进行拷贝,重新分配了一块内存块,那构造函数没有被执行也是非常正常的了

浅拷贝

  • 浅拷贝:Java偷懒的拷贝动作,Object类提供的方法clone只是拷贝对象,其对象的数组,引用对象等都不拷贝,还是指向原生对象的内部元素地址。两个对象共享一个私有变量,你改我改大家都改,是一种不安全的方式【拷贝的有:原始类型比如int,long,char等,String就把它当成基本类型--它的处理机制比较麻烦一点(它没有clone方法,通过字符串池stringcool在需要时才在内存中创建新的字符串)】
  • 使用原型模型时,引用的成员变量必须满足两个条件才不被拷贝

    1. 类的成员变量,而不是方法内变量2. 必须是一个可变对象的引用对象,而不是一个原始类型和一个不变对象
    • 例子

      package com.peng.yuanxing;import java.util.ArrayList;/** * @author kungfu~peng * @data 2017年11月19日 * @description */public class Thing implements Cloneable {    private ArrayList<String> list = new ArrayList<String>();    @Override    protected Thing clone() throws CloneNotSupportedException {        Thing thing = null;        try {            thing = (Thing) super.clone();        } catch (CloneNotSupportedException e) {            e.printStackTrace();        }        return thing;    }    // 添加值    public void setValue(String value) {        list.add(value);    }    // 获取数据    public ArrayList<String> getValues() {        return this.list;    }    public static void main(String[] args) throws CloneNotSupportedException {        // 创建对象        Thing t0 = new Thing();        t0.setValue("1");        t0.setValue("2");        t0.setValue("3");        // 克隆对象        Thing coleThing = t0.clone();        coleThing.setValue("a");        coleThing.setValue("b");        coleThing.setValue("c");        // 打印数据        System.out.println(t0.getValues());        System.out.println(coleThing.getValues());    }}
    • 执行结果

      [1, 2, 3, a, b, c][1, 2, 3, a, b, c]

深拷贝

  • 深拷贝:

    • 例子

      package com.peng.yuanxing;import java.util.ArrayList;/** * @author kungfu~peng * @data 2017年11月19日 * @description */public class Thing implements Cloneable {    private ArrayList<String> list = new ArrayList<String>();    @Override    protected Thing clone() throws CloneNotSupportedException {        Thing thing = null;        try {            thing = (Thing) super.clone();            thing.list = (ArrayList<String>) this.list.clone();        } catch (CloneNotSupportedException e) {            e.printStackTrace();        }        return thing;    }    // 添加值    public void setValue(String value) {        list.add(value);    }    // 获取数据    public ArrayList<String> getValues() {        return this.list;    }    public static void main(String[] args) throws CloneNotSupportedException {        // 创建对象        Thing t0 = new Thing();        t0.setValue("1");        t0.setValue("2");        t0.setValue("3");        // 克隆对象        Thing coleThing = t0.clone();        coleThing.setValue("a");        coleThing.setValue("b");        coleThing.setValue("c");        // 打印数据        System.out.println(t0.getValues());        System.out.println(coleThing.getValues());    }}
    • 执行结果

      [1, 2, 3][1, 2, 3, a, b, c]

注意

  • 深拷贝和浅拷贝建议不要混合使用,特别是在涉及类的继承时,父类有多个引用的情况就非常复杂,建议的方案是深拷贝和浅拷贝分开实现

clone与final(用冤家来记)

  • 例:增加final修饰,编译报错

    package com.peng.yuanxing;import java.util.ArrayList;/** * @author kungfu~peng * @data 2017年11月19日 * @description */public class Thing implements Cloneable {    private final ArrayList<String> list = new ArrayList<String>();    @Override    protected Thing clone() throws CloneNotSupportedException {        Thing thing = null;        try {            thing = (Thing) super.clone();            thing.list = (ArrayList<String>) this.list.clone();//编译报错:The final field Thing.list cannot be assigned        } catch (CloneNotSupportedException e) {            e.printStackTrace();        }        return thing;    }    // 添加值    public void setValue(String value) {        list.add(value);    }    // 获取数据    public ArrayList<String> getValues() {        return this.list;    }}
  • java中,final修饰的不可以被修改,引用数据类型则为地址不可变,list的地址改变了,自然就报错

最佳实践

  • 原型模型产生出大量信息相同的类,然后可以拷出副本,然后进行修改信息
  • 一个对象的起步可以不用从零起步,直接从一个已经具有雏形的对象克隆,然后再修改为生产的对象

声明

  • 摘自秦小波《设计模式之禅》第2版;
  • 仅供学习,严禁商业用途;
  • 代码手写,没有经编译器编译,有个别错误,自行根据上下文改正。
原创粉丝点击