设计模式之原型模式

1、原型模式介绍

1.1、定义

原型模式(Prototype Design Pattern)用一个已经创建的实例作为原型,通过复制该原型

对象来创建一个和原型对象相同的新对象。

原型模式也是一个创建型模式。

1.2、原型模式主要解决的问题

如果创建对象的成本比较大,比如对象中的数据是经过复杂计算才能得到,或者需要从

RPC接口或者数据库等比较慢的IO中获取,这种情况我们就可以使用原型模式,从其他

已有的对象中进行拷贝,而不是每次都创建新对象,进行一些耗时的操作。

2、原型模式原理

原型模式类图如下:

原型模式角色如下:

1)抽象原型类(Prototype):它是声明克隆方法的接口,是所有具体原型类的公共父类,

它可以是抽象类也可以是接口

2)具体原型类(ConcretePrototype):实现在抽象原型类中声明的克隆方法,在克隆方

法中返回自己的一个克隆对象

3)客户类(Client):在客户类中,让一个原型对象克隆自身从而创建一个新的对象。由于

客户类针对抽象原型类Prototype编程.因此用户可以根据需要选择具体原型类,系统具

有较好的扩展性,增加或者替换具体原型类都比较方便

3、原型模式应用示例

应用示例:

模拟某银行电子账单系统的广告信发送功能,广告信的发送都是有一个模板的,从数据库

查出客户的信息,然后放到模板中生成一份完整的邮件,然后交给发送机进行发送处理

3.1、发送广告信UML类图如下:

3.2、示例代码(一)

不采用设计模式,实现发送广告信,每次发送邮件时,需要new 创建一个新的Mail对象,

示例代码如下:

广告模版类 AdvTemplate

java 复制代码
/**
 * 广告信模板代码
 *
 **/
public class AdvTemplate {

    //广告信名称
    private String advSubject = "xx银行本月还款达标,可抽iPhone 13等好礼!";

    //广告信内容
    private String advContext = "达标用户请在2022年3月1日到2022年3月30参与抽奖......";

    public String getAdvSubject() {
        return this.advSubject;
    }

    public String getAdvContext() {
        return this.advContext;
    }
}

邮件类 Mail

java 复制代码
/**
 * 邮件类
 *
 **/
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();
    }

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

java 复制代码
/**
 * 业务场景类
 * 
 **/
public class Client {

    //发送信息的是数量,这个值可以从数据库获取
    private static int MAX_COUNT = 6;

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

    public static void main(String[] args) {

        //模拟邮件发送
        int i = 0;

        //把模板定义出来,数据是从数据库获取的
        Mail mail = new Mail(new AdvTemplate());
        mail.setTail("xxx银行版权所有");
        while(i < MAX_COUNT){
            //下面是每封邮件不同的地方
            mail.setAppellation(" 先生 (女士)");
            Random random = new Random();
            int num = random.nextInt(9999999);
            mail.setReceiver(num+"@"+"qq.com");
            //发送 邮件
            sendMail(mail);
            i++;
        }
    }
}

上边代码存在的问题:

1)发送邮件需要重复创建Mail类对象,而且Mail类的不同对象之间差别非常小,这样

重复的创建操作十分的浪费资源。

2)这种情况我们就可以使用原型模式,从其他已有的对象中进行拷贝,而不是每次都

创建新对象,进行一些耗时的操作。

3.3、示例代码(二)

采用 "原型模式" 重构邮件类 Mail,使每次发送新的邮件时,不用new 创建新的Mail 对象,

而是在原有的Mail对象上克隆得到。

示例代码如下:

重构邮件类Mail

java 复制代码
/**
 * 邮件类 实现Cloneable接口,表示该类的实例可以被复制
 *
 **/
public class Mail implements Cloneable{

    //收件人
    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();
    }

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

客户类

java 复制代码
/**
 * 业务场景类
 *
 **/
public class Client {

    //发送信息的是数量,这个值可以从数据库获取
    private static int MAX_COUNT = 6;

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

    public static void main(String[] args) {

        //模拟邮件发送
        int i = 0;

        //把模板定义出来,数据是从数据库获取的
        Mail mail = new Mail(new AdvTemplate());
        mail.setTail("xxx银行版权所有");
        while(i < MAX_COUNT){
            //下面是每封邮件不同的地方
            Mail cloneMail = mail.clone();
            cloneMail.setAppellation(" 先生 (女士)");
            Random random = new Random();
            int num = random.nextInt(9999999);
            cloneMail.setReceiver(num+"@"+"qq.com");
            //发送 邮件
            sendMail(cloneMail);
            i++;
        }
    }
}

4、原型模式总结

4.1、原型模式优点

1)当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一

个已有实例可以提高新实例的创建效率

2)原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相

同的工厂等级结构(具体工厂对应具体产品),而原型模式就不需要这样,原型模式的

产品复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。

3)可以使用深克隆的方式保存对象状态,使用原型模式将对象复制一份并将其状态保存

起来,以便在需要的时候使用,比如恢复到某一历史状态,可以辅助实现撤销操作

4.2、原型模式缺点

1)需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类

进行改造时需要修改源代码,违背了开闭原则

4.3、原型模式使用场景

1)资源优化场景,也就是当进行对象初始化需要使用很多外部资源时,比如,IO 资源、

数据文件、CPU、网络和内存等。

2)复杂的依赖场景, 比如,F 对象的创建依赖 A,A 又依赖 B,B 又依赖 C......于是创

建过程是一连串对象的 get 和 set。

3)性能和安全要求的场景, 比如,同一个用户在一个会话周期里,可能会反复登录平台

或使用某些受限的功能,每一次访问请求都会访问授权服务器进行授权,但如果每次

都通过 new 产生一个对象会非常烦琐,这时则可以使用原型模式。

4)同一个对象可能被多个修改者使用的场景, 比如,一个商品对象需要提供给物流、

会员、订单等多个服务访问,而且各个调用者可能都需要修改其值时,就可以考虑使

用原型模式。

5)需要保存原始对象状态的场景,比如,记录历史操作的场景中,就可以通过原型模式

快速保存记录。

相关推荐
岁岁岁平安1 分钟前
Java基础(Arrays工具类)(asList()方法)(详细)
java·开发语言·集合·arrays·aslist
窦再兴2 分钟前
关于Java中的List<User>如何进行深拷贝
java·list
WHabcwu12 分钟前
Spring Web MVC⼊⻔
java·后端·spring·mvc
coffee_baby14 分钟前
《解锁高效流程设计:深度剖析责任链模式与实战应用》
java·开发语言·责任链模式
customer0817 分钟前
【开源免费】基于SpringBoot+Vue.JS服装销售平台(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源·intellij-idea
晴子呀20 分钟前
一个有趣的编程题实战----交替打印线程
java·开发语言
叶辰 .38 分钟前
POI获取模板文件,替换数据横纵动态表格、折线图、饼状图、折线饼状组合图
java
胡净1 小时前
java并发线程02
java·开发语言
OLDERHARD1 小时前
Java — LeetCode 面试经典150题(一)
java·算法·leetcode
聆听HJ2 小时前
Java日期格式化注解@DateTimeFormat和@JsonFormat
java·开发语言