16. 原型模式

定义

原型模式(Prototype Pattern)的定义如下:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

通俗理解

我们生活中经常会用到复印机,用来复印书籍、简历、身份证、银行卡... ...但是,有没有想过,为什么我们书籍、简历这些,要选择复印,而不是重新排版,打印一份呢?(在讨论一些理所当然的东西的时候,总会有一股哲学的味道)。你可以说,这很理所当然呀!重新排版还得消耗人力物力,反正书籍、简历这些都是一样的,直接复印一份不就行了么?

原型模式,就是复印简历的过程,简历都差不多,选择以前的简历进行复印,那么可以节省排版、打印的人力物力。在程序中也是一样,创建差不多的对象,我们可以通过new对象来创建,这样的一种方式会导致new出对象之后,还要对它们的属性进行setter,然后才能够使用。如果我们使用原型模式,对旧的对象进行复制,然后修改它们不同的属性,这一方面可以避免了大量的setter;另外一个方面是节约了资源,创建对象更快,更节省时间(《JVM——Java关键字背后的故事》会有详细的比较与原理,未写)。

示例

业务为复制简历。

渣渣程序

不按照原型模式来,我们可以写出这样的程序:

简历

public class CV {
    /**
     * 名字
     */
    private String name;
    /**
     * 年龄
     */
    private int age;
    /**
     * 技能
     */
    private String skill;
    /**
     * 住址
     */
    private Address address;
   
    // setter和getter省略
}

住址

public class Address {
    /**
     * 国家
     */
    private String country;
    /**
     * 省
     */
    private String province;
    /**
     * 市
     */
    private String city;
    /**
     * 街道
     */
    private String street;
    //setter和getter省略
}

调用方

public class Main {
    public static void main(String[] args) {
        //Jack的简历1
        Address jackAddr1 = new Address();
        jackAddr1.setCountry("China");
        jackAddr1.setProvince("Guangdong");
        jackAddr1.setCity("Shenzhen");
        jackAddr1.setStreet("Baoan");

        CV jack1 = new CV();
        jack1.setName("Jack");
        jack1.setAge(24);
        jack1.setSkill("coding");
        jack1.setAddress(jackAddr1);

        //后来Jack搬家,搬到西乡
        Address jackAddr2 = new Address();
        jackAddr2.setCountry("China");
        jackAddr2.setProvince("Guangdong");
        jackAddr2.setCity("Shenzhen");
        jackAddr2.setStreet("Xixiang");

        CV jack2 = new CV();
        jack2.setName("Jack");
        jack2.setAge(24);
        jack2.setSkill("coding");
        jack2.setAddress(jackAddr2);
    }
}

可怜🤕的Jack,因为家搬到西乡了,就要重新修改一份简历,真是费时费力。

优化

类图

image

通过这个类图,就可以知道,最关键的是clone方法。因此,原型模式,最关键的是写好clone方法。

程序

浅克隆

Java里面有一个声明式的接口Cloneable,这个接口本身没有写任何的方法,只是告诉JVM,该接口的实现类需要开放“克隆”功能,而且在Object当中,clone是一个native方法,表示是JVM直接提供的方法(《JVM——Java关键字背后的故事》会有详细的比较与原理,未写),我们实现这个接口,并重写Object的clone方法就可以了完成对象的拷贝了。

简历

public class CV implements Cloneable{
    /**
     * 名字
     */
    private String name;
    /**
     * 年龄
     */
    private int age;
    /**
     * 技能
     */
    private String skill;
    /**
     * 住址
     */
    private Address address;
    /**
     * 等级
     */
    private Integer level;
  
    // setter和getter省略... ...
 
    public CV clone() {
        Object cloneCV = null;
        try {
            cloneCV = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return (CV)cloneCV;
    }
}

地址

public class Address{
    /**
     * 国家
     */
    private String country;
    /**
     * 省
     */
    private String province;
    /**
     * 市
     */
    private String city;
    /**
     * 街道
     */
    private String street;
}

调用方

public class Main {
    public static void main(String[] args) {
        //Jack的简历1
        Address jackAddr1 = new Address();
        jackAddr1.setCountry("China");
        jackAddr1.setProvince("Guangdong");
        jackAddr1.setCity("Shenzhen");
        jackAddr1.setStreet("Baoan");

        CV jack1 = new CV();
        jack1.setName("Jack");
        jack1.setAge(24);
        jack1.setSkill("coding");
        jack1.setLevel(1);
        jack1.setAddress(jackAddr1);

        //后来Jack搬家,搬到西乡
        CV jack2 = jack1.clone();
        jack2.getAddress().setStreet("Xixiang");
        jack2.setAge(26);
        jack2.setName("Mack");
        jack2.setLevel(3);

        System.out.println(jack1.getAddress().getStreet());//Xixiang
        System.out.println(jack2.getAddress().getStreet());//Xixiang
        System.out.println(jack1.getAge());//24
        System.out.println(jack2.getAge());//26
        System.out.println(jack1.getName());//Jack
        System.out.println(jack2.getName());//Mack
        System.out.println(jack1.getLevel());//1
        System.out.println(jack2.getLevel());//3
    }
}

通过上面的方式,就可以对对象进行拷贝。很不幸的是,super.clone()只是对对象的浅克隆,只有基本类型(int,boolean等)和String会被复制新的一份,而引用类型(Integer,类,接口,数组等),还是会指向原来的内存地址,造成Jack搬到Xixiang后,第一份简历和第二份简历都被改成Xixiang

深克隆

如果我们也需要把引用类型的属性也复制一份,我们应该怎么改?

序列化,反序列化

public CV clone() {
        CV cv = null;
        try {
            // 将对象写入到流
            FileOutputStream fos = new FileOutputStream("clone.out");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(this);
            // 从流中写入到对象
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("clone.out"));
            cv = (CV) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cv;
    }
}

逐个属性克隆

public CV clone() {
        CV cv = null;
        try {
            cv = (CV) super.clone();
            cv.address = this.address.clone();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cv;
    }

优点

  1. 直接在内存中创建对象,提高创建对象的效率
  2. 不使用构造器创建对象,减少了束缚
  3. 可以保存对象的状态,可应用于对象的撤销操作

缺点

  1. 需要克隆对象以及引用的类型都实现clone方法,否则用不了。如果引用嵌套比较深,那么就需要一层一层地去修改。
  2. 不使用构造器创建对象,减少了束缚

应用场景

  1. 资源优化,类的初始化需要很多资源的,包含数据和硬件资源
  2. 性能和安全要求比较高
  3. 一个对象多个修改者,拷贝出备份的让修改者去修改

注意事项

  1. clone()无需调用构造器就能创建对象
  2. final修饰的属性无法使用clone()方法
  3. 最好别用

https://www.jianshu.com/p/51558afcd9c0

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

推荐阅读更多精彩内容