原型模式——Prototype

案例展示——Prototype怎么用?

 原型模式在开发过程中或许名头没有单例模式,工厂方法模式等设计模式那么响亮,但却是一个应用场景很多的设计模式,下面就通过一个案例来了解这个设计模式是什么。

 随着互联网时代的来临,百花齐放,百家争鸣,各种各样的业务都依托互联网而发展的越来越繁荣。而诸多电信公司无疑是互联网产业中属于风口浪尖的产业,因为它们掌控互联网中最核心的源动力——流量。虽然卖流量卖话费已经让诸多电信公司赚得盆满泵满,但是哪个公司又会觉得赚得多呢?这不,随着邮件业务的兴起,越来越多的人喜欢上了这种快捷方便的交流方式,于是,各电信公司的目光都瞄上这个东西:

  • 可不可以依托这个平台干点啥?

  • 我数据库里面那么多人,平常节假日什么的做个活动啊什么的可不可以发一封广告邮件给他?

 看看,业务就这样产生了。所以说需求这种东西都是铜臭味下的产物,没有什么业务是钱刺激不出来的。现在来考虑具体场景吧:

  • 批量给数据库中业务对象发送广告邮件,并且每封邮件都需要进行私人定制:比如称谓是业务对象的名字。

  • 批量发送邮件可能会被反垃圾邮件引擎误认为是垃圾邮件,所以需要在头部加一些伪造数据。

根据以上需求,可以得到设计类图如下:

image

  • Mail类是一个业务对象,包含了发一封邮件所需要的基本信息,给用户发送

  • AdvTemplate是广告信的模板

如下是代码实现:

//广告信模板
public class AdvTemplate {
    //广告信名称
    private String advSubject = "xx电信公司国庆充话费送手机活动";
    //广告信内容
    private String advContext = "国庆期间充话费满500元送一部华为手机!";

    //取得广告信名称
    public String getAdvSubject() {
        return this.advSubject;
    }
    //取得广告信内容
    public String getAdvContext() {
        return this.advContext;
    }
}

//mail业务类
public class Mail {

    //收件人
    private String receiver;
    //邮件名称
    private String subject;
    //称谓
    private String application;
    //邮件内容
    private String context;
    //邮件尾部,例如:xx版权所有
    private String tail;

    public Mail(AdvTemplate advTemplate) {
        this.context = advTemplate.getAdvContext();
        this.subject = advTemplate.getAdvSubject();
    }

    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;
    }
}

在一个场景中运行实现得:

public class Client {
    //发送邮件的数量
    private static int MAX_COUNT = 3;
    
    public static void main(String[] args) {
        int i = 0;
        //定义模板
        Mail mail = new Mail(new AdvTemplate());
        //模拟发送场景
        while (i < MAX_COUNT) {
            mail.setApplication(getRandomString(5) + " 先生(女士) ");
            mail.setReceiver(getRandomString(5) + "@" + getRandomString(8) + ".com");
            sendMail(mail);
            i++;
        }
    }

    //发送邮件
    public static void sendMail(Mail mail) {
        System.out.println("标题:" + mail.getSubject() + "\t收件人:" + mail.getReceiver() + "\t...发送成功!");
    }
    
    //获得指定长度的随机字符串
    public static String getRandomString(int maxLength) {
        String source = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        StringBuffer sb = new StringBuffer();
        Random random = new Random();
        for (int i = 0; i < maxLength; i++) {
            sb.append(source.charAt(random.nextInt(source.length())));
        }
        return sb.toString();
    }
}

//结果如下:
标题:xx电信公司国庆充话费送手机活动	收件人:[email protected]	...发送成功!
标题:xx电信公司国庆充话费送手机活动	收件人:[email protected]	...发送成功!
标题:xx电信公司国庆充话费送手机活动	收件人:[email protected]	...发送成功!

 看到实现结果,长舒了一口气,终于实现了。可是,仔细观察一下我们的设计,会发现存在很大的问题:程序是单线程的,批量发送耗时太长,假如每天要给600万用户发送邮件,按照一封0.02秒计算,需要33小时。。。这样的话,老板可能会让你打包走人了。也许可以在sendMail()上使用多线程,但是会出现线程安全问题,于是为了规避线程安全问题进行各种并发操作估计还是达不到要求。难道没有解决之法了?当然不是,原型模式闪亮登场啦,看下面的设计:

image

 乍一看可能感觉和第一个设计没啥区别,无非不就是多了一个Cloneable接口和clone()方法吗?可这就是关键所在(克隆,有没有触碰到你的知识盲区啊?),既然创建一个对象很慢,为啥不只创建一个,然后在内存中直接复制出来呢,然后进行局部修改就可以了。

下面是代码实现与结果:

public class AdvTemplate {
    //广告信名称
    private String advSubject = "xx电信公司国庆充话费送手机活动";
    //广告信内容
    private String advContext = "国庆期间充话费满500元送一部华为手机!";

    //取得广告信名称
    public String getAdvSubject() {
        return this.advSubject;
    }
    //取得广告信内容
    public String getAdvContext() {
        return this.advContext;
    }
}

public class Mail implements Cloneable {

    //收件人
    private String receiver;
    //邮件名称
    private String subject;
    //称谓
    private String application;
    //邮件内容
    private String context;
    //邮件尾部,例如:xx版权所有
    private String tail;

    public Mail(AdvTemplate advTemplate) {
        this.context = advTemplate.getAdvContext();
        this.subject = advTemplate.getAdvSubject();
    }

    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;
    }

    @Override
    public Mail clone() {
        Mail mail = null;
        try {
            mail = (Mail) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return mail;
    }
}

public class Client {
    //发送邮件的数量
    private static int MAX_COUNT = 8;

    public static void main(String[] args) {
        int i = 0;
        //定义模板
        Mail mail = new Mail(new AdvTemplate());
        //模拟发送场景
        while (i < MAX_COUNT) {
            Mail cloneMail = mail.clone();
            cloneMail.setApplication(getRandomString(5) + " 先生(女士) ");
            cloneMail.setReceiver(getRandomString(5) + "@" + getRandomString(8) + ".com");
            sendMail(cloneMail);
            i++;
        }
    }

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

    //获得指定长度的随机字符串
    public static String getRandomString(int maxLength) {
        String source = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        StringBuffer sb = new StringBuffer();
        Random random = new Random();
        for (int i = 0; i < maxLength; i++) {
            sb.append(source.charAt(random.nextInt(source.length())));
        }
        return sb.toString();
    }
}

//结果如下:
标题:xx电信公司国庆充话费送手机活动	收件人:[email protected]	...发送成功!
标题:xx电信公司国庆充话费送手机活动	收件人:[email protected]	...发送成功!
标题:xx电信公司国庆充话费送手机活动	收件人:[email protected]	...发送成功!

这种不通过new关键字来产生一个对象,而是通过对象复制来实现的模式叫做原型模式。

深入分析——Prototype是什么?

Prototype的定义

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

原型模式的通用类图如下:

image

原型模式的核心是clone()方法,通过该方法进行对象的拷贝,Java提供了一个Cloneable接口来表示这个对象是可拷贝的,Cloneable接口中没有任何方法,只是作为一种标记,只有实现了该接口的对象才会被JVM拷贝。

原型模式通用源码如下;

public class PrototypeClass implements Cloneable {
    //重写父类Object方法
    @Override
    public PrototypeClass clone() {
        PrototypeClass p = null;
        try {
            p = (PrototypeClass)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return p;
    }
}

关于clone可以参考:https://blog.csdn.net/king123456man/article/details/81740255

Prototype延伸
Prototype的注意事项

注意: 一个实现了Cloneable接口并重写了clone()方法的类,先创建一个对象,然后通过这个对象调用clone()方法进行复制,在对象复制的时候该类的构造器不会执行。

 Object类的clone()方法的原理是从内存中(堆内存)以二进制流的方式进行拷贝,重新分配一个内存块。而构造器用于初始化时是在堆内存中开辟空间创建对象的时候,拷贝并不是对象的完整创建过程,从这里理解的话,不应该执行构造器。

Prototype的优点
  • 性能优良:原型模式是在内存中拷贝二进制流,要比直接new一个对象好得多,特别是在一个循环体内产生大量对象时,原型模式能更好的体现其优点。

  • 逃避构造函数的约束:直接在内存中拷贝,不会执行构造函数,减少了约束

Prototype的使用场景
  • 资源优化场景:类初始化需要消耗非常多的资源,所以在需要使用大量同一对象的时候可以考虑原型模式

  • 性能和安全要求的场景:通过new产生一个对象要非常繁琐的数据准备或访问权限,可以使用原型模式替代

  • 一个对象多个修改者场景:一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改该对象的值,可以使用原型模式拷贝多个对象供调用者使用

 在项目开发中,原型模式一般和工厂方法模式出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。

参考

《设计模式之禅》

猜你喜欢

转载自blog.csdn.net/king123456man/article/details/82756411