原型模式和java拷贝

原型模式

定义

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

特点

不需要知道创建的细节,不调用构造函数

类型

创建型

适用场景
  1. 类初始化消耗较多资源
  2. new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
  3. 构造函数比较复杂
  4. 循环体中生产大量对象时
优点
  1. 原型模式性能比直接new 一个对象性能高
  2. 简化创建过程
缺点
  1. 必须配备克隆方法
  2. 对克隆复杂对象或克隆出的对象进行复杂改造时,容易引入风险。
  3. 深拷贝、浅拷贝必须引用得当

下面看代码,写代码之前我们先假设一个业务场景,假设我们现在要发一个构建一个邮件对象给别人发送邮件告诉别人中奖了,这个对象构建起来非常麻烦,当然我们的代码不一定会构建非常复杂的对象。

邮件工具类

public class MailUtil {
    public static void sendMail(Mail mail){
        String outputContent = "向{0}同学,邮件地址:{1},邮件内容:{2}发送邮件成功";
        System.out.println(MessageFormat.format(outputContent,mail.getName(),mail.getEmailAddress(),mail.getContent()));
    }
    public static void saveOriginMailRecord(Mail mail){
        System.out.println("存储originMail记录,originMail:"+mail.getContent());
    }
}

邮件实体类

public class Mail implements Cloneable{
    private String name;
    private String emailAddress;
    private String content;
    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;
    }

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        System.out.println("clone mail object");
        return super.clone();
    }
}

这个实体类注意要实现Cloneable接口,然后重写Object的clone方法,直接返回父类的的clone方法就可以了,我们在里面添加了一个输入方便等会儿我们测试。下面看测试类。

测试类

public class PrototypeTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        Mail mail = new Mail();
        mail.setContent("初始化模板");
        System.out.println("初始化mail:"+mail);
        for(int i = 0;i < 10;i++){
            Mail mailTemp = (Mail) mail.clone();
            mailTemp.setName("姓名"+i);
            mailTemp.setEmailAddress("姓名"+i+"@redstar.com");
            mailTemp.setContent("恭喜您,你中奖了");
            MailUtil.sendMail(mailTemp);
            System.out.println("克隆的mailTemp:"+mailTemp);
        }
        MailUtil.saveOriginMailRecord(mail);
    }
}

然后我们看看测试类运行结果。

克隆的mailTemp:Mail{name='姓名4', emailAddress='姓名4@redstar.com', content='恭喜您,你中奖了'}com.design.pattern.creational.prototype.Mail@312b1dae
clone mail object
向姓名5同学,邮件地址:姓名5@redstar.com,邮件内容:恭喜您,你中奖了发送邮件成功
克隆的mailTemp:Mail{name='姓名5', emailAddress='姓名5@redstar.com', content='恭喜您,你中奖了'}com.design.pattern.creational.prototype.Mail@7530d0a
clone mail object
向姓名6同学,邮件地址:姓名6@redstar.com,邮件内容:恭喜您,你中奖了发送邮件成功
克隆的mailTemp:Mail{name='姓名6', emailAddress='姓名6@redstar.com', content='恭喜您,你中奖了'}com.design.pattern.creational.prototype.Mail@27bc2616
clone mail object
向姓名7同学,邮件地址:姓名7@redstar.com,邮件内容:恭喜您,你中奖了发送邮件成功
克隆的mailTemp:Mail{name='姓名7', emailAddress='姓名7@redstar.com', content='恭喜您,你中奖了'}com.design.pattern.creational.prototype.Mail@3941a79c
clone mail object
向姓名8同学,邮件地址:姓名8@redstar.com,邮件内容:恭喜您,你中奖了发送邮件成功
克隆的mailTemp:Mail{name='姓名8', emailAddress='姓名8@redstar.com', content='恭喜您,你中奖了'}com.design.pattern.creational.prototype.Mail@506e1b77
clone mail object
向姓名9同学,邮件地址:姓名9@redstar.com,邮件内容:恭喜您,你中奖了发送邮件成功
克隆的mailTemp:Mail{name='姓名9', emailAddress='姓名9@redstar.com', content='恭喜您,你中奖了'}com.design.pattern.creational.prototype.Mail@4fca772d
存储originMail记录,originMail:初始化模板

注意看打印结果的内存地址,他们打印的地址是不一样的,他们不是同一个对象。使用clone方式操作的是内存中的二进制文件,比直接new一个对象性能好的多。

深克隆和浅克隆

既然讲到了clone那我们就来说说深克隆和浅克隆吧。关于这个我们看一段代码。

public class Pig implements Cloneable{
    private String name;
    private Date birthday;

    public Pig(String name, Date birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }

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

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "Pig{" +
                "name='" + name + '\'' +
                ", birthday=" + birthday +
                '}'+super.toString();
    }
}

这段代码我们定义了一个pig小猪佩奇类,有两个属性名字和生日,我们写一下他的测试类。

public class PigTest {
    public static void main(String[] args) throws CloneNotSupportedException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Date birthday = new Date(0L);
        Pig pig1 = new Pig("佩奇",birthday);
        Pig pig2 = (Pig) pig1.clone();
        System.out.println(pig1);
        System.out.println(pig2);
    }
}

我们运行一下看看结果

Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com.design.pattern.creational.prototype.clone.Pig@27973e9b
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com.design.pattern.creational.prototype.clone.Pig@312b1dae

注意看内存地址,好像是符合我们的预期,内存地址是不一样的,那我们修改一下,这个生日属性看看会发生什么

public class PigTest {
    public static void main(String[] args) throws CloneNotSupportedException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Date birthday = new Date(0L);
        Pig pig1 = new Pig("佩奇",birthday);
        Pig pig2 = (Pig) pig1.clone();
        System.out.println(pig1);
        System.out.println(pig2);
        pig1.getBirthday().setTime(666666666666L);
        System.out.println(pig1);
        System.out.println(pig2);
    }
}

运行结果:

Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com.design.pattern.creational.prototype.clone.Pig@27973e9b
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com.design.pattern.creational.prototype.clone.Pig@312b1dae
Pig{name='佩奇', birthday=Sat Feb 16 09:11:06 CST 1991}com.design.pattern.creational.prototype.clone.Pig@27973e9b
Pig{name='佩奇', birthday=Sat Feb 16 09:11:06 CST 1991}com.design.pattern.creational.prototype.clone.Pig@312b1dae

仔细看虽然他们的内存地址不一样但是在修改了其中一个生日时发现两个都改变了。这是因为我们的这种方式实现的是一种浅拷贝,生日使用的是date类型是一个引用类型,这两个对象引用的都是同一个地址。为了得到我们期待的结果我们就要修改为深拷贝的方式。

public class Pig implements Cloneable{
    private String name;
    private Date birthday;

    public Pig(String name, Date birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }

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

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Pig pig = (Pig)super.clone();

        //深克隆
        pig.birthday = (Date) pig.birthday.clone();
        return pig;
    }

    @Override
    public String toString() {
        return "Pig{" +
                "name='" + name + '\'' +
                ", birthday=" + birthday +
                '}'+super.toString();
    }
}

此时我们再看看测试类的运行结果:

Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com..design.pattern.creational.prototype.clone.Pig@27973e9b
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com.design.pattern.creational.prototype.clone.Pig@312b1dae
Pig{name='佩奇', birthday=Sat Feb 16 09:11:06 CST 1991}com.design.pattern.creational.prototype.clone.Pig@27973e9b
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com.design.pattern.creational.prototype.clone.Pig@312b1dae

此时结果就是我们想要的了。对于深拷贝还有一种方式就是通过json序列化一次成json再讲json转成对象,这样也可以实现深拷贝,但是我们说使用拷贝的目的是拷贝是直接修改二进制文件效率高,至于使用转json的方式效率怎么样我不太清楚,欢迎小伙伴们自己试验一下把结果和我交流一下。今天的原型型模式就到这里。

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

推荐阅读更多精彩内容