6. 原型模式(Prototype Pattern)

定义

  • 原型模式指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
  • 不需要知道任何创建的细节,不调用构造函数

类型

创建型

适用场景

  • 类初始化消耗较多的资源
  • new 产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
  • 构造函数比较复杂
  • 循环体种产生大量对象

优点

  • 原型模式性能比直接new一个对象性能高
  • 简化创建过程

缺点

  • 必须配备克隆方法,重写Object的clone()
  • 对克隆复杂对象或对克隆出的对象进行复杂改造时,容易引人风险
  • 深拷贝、浅拷贝要运用得当,否则也很容易引人风险。

扩展

  • 深克隆(对应引用类型,如果需要他们指向不同的对象,一定用深克隆
  • 浅克隆

代码示例

模拟一个场景,假设我们要给人家发送邮件

  1. 创建一个邮件类
public class Mail {

    private String name;

    private String emailAddress;

    private String content;

    private Date sendTime;

    public Mail() {
        System.out.println("Mail Class Constructor");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmailAddress() {
        return emailAddress;
    }

    public void setEmailAddress(String emailAddress) {
        this.emailAddress = emailAddress;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Date getSendTime() {
        return sendTime;
    }

    public void setSendTime(Date sendTime) {
        this.sendTime = sendTime;
    }


    @Override
    public String toString() {
        return "Mail{" +
                "name='" + name + '\'' +
                ", emailAddress='" + emailAddress + '\'' +
                ", content='" + content + '\'' +
                ", sendTime=" + sendTime +
                '}';
    }
}
  1. 创建邮件工具类
public class MailUtil {

    public static void sendMail(Mail mail) {
        String outputContent = "时间:{0},向{1},邮件地址:{2},邮件内容:{3},发送邮件";
        System.out.println(MessageFormat.format(outputContent, mail.getSendTime(), mail.getName(), mail.getEmailAddress(), mail.getContent()));
    }

    public static void saveOriginMailRecord(Mail mail){
        System.out.println("存储原始邮件记录内容,"+mail.getContent());
    }
}

  1. 一般调用模拟
public class Test {
    public static void main(String[] args) {
        Mail mail = new Mail();
        mail.setContent("初始化邮件模板");
        for (int i = 0; i < 2; i++) {
            mail.setName("姓名"+i);
            mail.setEmailAddress("姓名"+i+"@oiobee.com");
            mail.setContent("哈喽你好");
            mail.setSendTime(new Date(0L));
            MailUtil.sendMail(mail);
        }
        MailUtil.saveOriginMailRecord(mail);
    }
}

执行结果

Mail Class Constructor
时间:70-1-1 上午8:00,向姓名0,邮件地址:姓名0@oiobee.com,邮件内容:哈喽你好,发送邮件
时间:70-1-1 上午8:00,向姓名1,邮件地址:姓名1@oiobee.com,邮件内容:哈喽你好,发送邮件
存储原始邮件记录内容,哈喽你好

假设我们在发送完邮件后,需要保存邮件模板我们发现这样的方式,是不可以的,现在存储的是for循环中最后一次set的结果

现在呢,我们就引人原型模式看一下如何解决这个问题
原型模式:是在内存中进行二进制流的拷贝,比new一个对象的性能要好一下。假设我们mail的对象初始化非常复杂,并且在for循环中会产生大量的对象,在这种情况下用原型模式是非常合适的。

步骤

  1. 需要将Mail类具有克隆功能,具体做法就是让该类实现Cloneable接口,然后重写clone()
//1.实现ICloneable
//2.重写父类clone()
public class Mail implements Cloneable{

    private String name;

    private String emailAddress;

    private String content;

    private Date sendTime;

    public Mail() {
        System.out.println("Mail Class Constructor");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmailAddress() {
        return emailAddress;
    }

    public void setEmailAddress(String emailAddress) {
        this.emailAddress = emailAddress;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Date getSendTime() {
        return sendTime;
    }

    public void setSendTime(Date sendTime) {
        this.sendTime = sendTime;
    }


    @Override
    public String toString() {
        return "Mail{" +
                "name='" + name + '\'' +
                ", emailAddress='" + emailAddress + '\'' +
                ", content='" + content + '\'' +
                ", sendTime=" + sendTime +
                '}' + super.toString();
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        //为了方便调试
        System.out.println("clone mail object");
        return super.clone();
    }
}

  1. 修改调用
    在for 循环中使用mail的clone()
public class Test {
    public static void main(String[] args) throws Exception{
        Mail mail = new Mail();
        mail.setContent("初始化邮件模板");
        System.out.println("mail"+mail.toString());
        for (int i = 0; i < 2; i++) {
            Mail mailTemp = (Mail)mail.clone();
            mailTemp.setName("姓名"+i);
            mailTemp.setEmailAddress("姓名"+i+"@oiobee.com");
            mailTemp.setContent("哈喽你好");
            mailTemp.setSendTime(new Date(0L));
            MailUtil.sendMail(mailTemp);
            System.out.println("mailTemp"+mailTemp.toString());
        }
        MailUtil.saveOriginMailRecord(mail);
    }
}

执行结果

Mail Class Constructor
mailMail{name='null', emailAddress='null', content='初始化邮件模板', sendTime=null}com.test.principle.pattern.creational.prototype.Mail@45ee12a7
clone mail object
时间:70-1-1 上午8:00,向姓名0,邮件地址:姓名0@oiobee.com,邮件内容:哈喽你好,发送邮件
mailTempMail{name='姓名0', emailAddress='姓名0@oiobee.com', content='哈喽你好', sendTime=Thu Jan 01 08:00:00 CST 1970}com.test.principle.pattern.creational.prototype.Mail@355da254
clone mail object
时间:70-1-1 上午8:00,向姓名1,邮件地址:姓名1@oiobee.com,邮件内容:哈喽你好,发送邮件
mailTempMail{name='姓名1', emailAddress='姓名1@oiobee.com', content='哈喽你好', sendTime=Thu Jan 01 08:00:00 CST 1970}com.test.principle.pattern.creational.prototype.Mail@4dc63996
存储原始邮件记录内容,初始化邮件模板

通过结果我们可以看出,满足了我们的要求,我们还发现调用clone方法的是,是不会调用Mail的构造器的。并且我们通过super.toString(),发现们在克隆出来的对象并不是同一个

踩坑

上代码

public class Test {
    public static void main(String[] args) throws Exception{
        //初始化一个时间
        Date time = new Date(0L);

        //初始化一个mail实例
        Mail mail = new Mail();
        mail.setName("姓名A");
        mail.setSendTime(time);

        //克隆一个mail实例
        Mail mailTemp = (Mail)mail.clone();
        //修改时间
        mailTemp.getSendTime().setTime(666666666666L);
        
        //打印两个实例内容
        System.out.println("mail"+mail.toString());
        System.out.println("mailTemp"+mailTemp.toString());

        //查看sendtime的hashCode
        System.out.println(mail.getSendTime().hashCode());
        System.out.println(mailTemp.getSendTime().hashCode());
    }
}

执行结果

Mail Class Constructor
clone mail object
mailMail{name='姓名A', emailAddress='null', content='null', sendTime=Sat Feb 16 09:11:06 CST 1991}com.test.principle.pattern.creational.prototype.Mail@7ea987ac
mailTempMail{name='姓名A', emailAddress='null', content='null', sendTime=Sat Feb 16 09:11:06 CST 1991}com.test.principle.pattern.creational.prototype.Mail@12a3a380
946735665
946735665

我们预期的结果是mail的SendTime 和 mailTemp是两个不相同的时间。但是结果却让我大失所望,两个时间竟然都变成了修改后的时间,我们通过sendTime的hashCode可以看出这个Date对象是同一个。还记得我们最开始的时候说的对应引用类型,如果需要他们指向不同的对象,一定用深克隆。所以要记住

好的现在我们修改一下我们mail的clone方法

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Mail mail = (Mail)super.clone();
        //对引用对象 深克隆(针对引用类型)
        mail.sendTime = (Date)mail.sendTime.clone();
        return mail;
    }

再次执行测试代码,执行结果

Mail Class Constructor
mailMail{name='姓名A', emailAddress='null', content='null', sendTime=Thu Jan 01 08:00:00 CST 1970}com.test.principle.pattern.creational.prototype.Mail@7ea987ac
mailTempMail{name='姓名A', emailAddress='null', content='null', sendTime=Sat Feb 16 09:11:06 CST 1991}com.test.principle.pattern.creational.prototype.Mail@12a3a380
0
946735665

这样从结果上看就是正常的了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值