原型模式

原型模式定义

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

通用类图如下:

原型模式通用类图、.png
public class PrototypeClass  implements Cloneable{
     //覆写父类Object方法
     @Override
     public PrototypeClass clone(){
             PrototypeClass prototypeClass = null;
             try {
                    prototypeClass = (PrototypeClass)super.clone();
             } catch (CloneNotSupportedException e) {
                    //异常处理
             }
             return prototypeClass;
     }
}

实现一个接口,然后重写clone方法,就完成了原型模式!很简单啊

示例

模拟信用卡中心批量发送广告信的场景

原型模式-发广告邮件示例.png

广告信模板

public class AdvTemplate {
     //广告信名称
     private String advSubject ="XX银行国庆信用卡抽奖活动";
     //广告信内容
     private String advContext = "国庆抽奖活动通知:只要刷卡就送你一百万!...";
     //取得广告信的名称
     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 appellation;
     //邮件内容
     private String contxt;
     //邮件的尾部,一般都是加上"XXX版权所有"等信息
     private String tail;
     //构造函数
     public Mail(AdvTemplate advTemplate){
             this.contxt = advTemplate.getAdvContext();
             this.subject = advTemplate.getAdvSubject();
     }
     @Override
     public Mail clone(){
             Mail mail =null;
             try {
                    mail = (Mail)super.clone();
             } catch (CloneNotSupportedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
             }
             return mail;
     }
     //以下为getter/setter方法
}

场景类

public class Client {
     //发送账单的数量,这个值是从数据库中获得
     private static int MAX_COUNT = 6;
     public static void main(String[] args) {
             //模拟发送邮件
             int i=0;
             //把模板定义出来,这个是从数据中获得
             Mail mail = new Mail(new AdvTemplate());
             mail.setTail("XX银行版权所有");
             while(i<MAX_COUNT){
                    //以下是每封邮件不同的地方
                    Mail cloneMail = mail.clone();
                    cloneMail.setAppellation(getRandString(5)+" 先生(女士)");
                    cloneMail.setReceiver(getRandString(5)+"@"+getRandString(8)+".com");
                    //然后发送邮件
                    sendMail(cloneMail);
                    i++;
             }
     }
    
    //发送邮件
     public static void sendMail(Mail mail){
             System.out.println("标题:"+mail.getSubject() + "\t收件人:
                "+mail.getReceiver()+"\t...发送成功!");
     }
     //获得指定长度的随机字符串
     public static String getRandString(int maxLength){
             String source ="abcdefghijklmnopqrskuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
             StringBuffer sb = new StringBuffer();
             Random rand = new Random();
             for(int i=0;i&lt;maxLength;i++){
                     sb.append(source.charAt(rand.nextInt(source.length())));
             }
             return sb.toString();
     }
}

sendMail()即使是多线程下也是没有关系的,mail.clone()这个方法,把对象复制一份,产生一个新对象,和原有对象一样,然后再修改细节的数据。这种不通过new关键字来产生一个对象,而是通过对象复制来实现的模式就叫做原型模式。

原型模式的应用

优点

  • 性能优良

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

  • 逃避构造函数的约束

    这既是它的优点也是缺点,直接在内存中拷贝,构造函数不会执行的,优点就是减少了约束,缺点也是减少了约束。

使用场景

  • 资源优化场景

    类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。

  • 性能和安全要求的场景

    通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。

  • 一个对象多个修改者场景

    一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型拷贝多个对象供调用者使用。

使用注意事项

构造函数不会执行

public class Thing implements Cloneable{
    public Thing() {
        System.out.println("----构造函数----");
    }

    @Override
    protected Thing clone() {
        Thing thing = null;
        try {
            thing = (Thing) super.clone();
        }catch (CloneNotSupportedException e){
            e.printStackTrace();
        }
        return thing;
    }

    public static void main(String[] args) {
        Thing t = new Thing();
        t.clone();
        /*
        执行结果:
        ----构造函数----
        */
    }
}

对象拷贝时构造函数确实没有被执行,这点从原理来讲也是可以讲得通的,Object类的clone方法的原理是从内存中(具体地说就是堆内存)以二进制流的方式进行拷贝,重新分配一个内存块,那构造函数没有被执行也是非常正常的了。

浅拷贝与深拷贝的问题

clone与final

对象的clone与对象内的final关键字是有冲突的

public class Thing implements Cloneable{
     //定义一个私有变量
     private ArrayList&lt;String&gt; arrayList = new ArrayList&lt;String&gt;();
     @Override
     public Thing clone(){
             Thing thing=null;
             try {
                    thing = (Thing)super.clone();
                    //编译器报错
                    thing.arrayList = (ArrayList&lt;String&gt;)this.arrayList.clone();
             } catch (CloneNotSupportedException e) {
                    e.printStackTrace();
             }
             return thing;
     }
}

浅拷贝

浅拷贝介绍

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

浅拷贝的特点
  1. 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个 .
  2. 对于引用类型,比如数组或者类对象,因为引用类型是引用传递,所以浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间。改变其中一个,会对另外一个也产生影响。

图示:

浅拷贝图示.png
浅拷贝的实现

实现对象拷贝的类,需要实现 Cloneable 接口,并覆写 clone() 方法。

public class Subject {
    private String name;
    //getter setter方法
}

public class Student implements Cloneable {
    //引用类型
    private Subject subject;
    //基础数据类型
    private String name;
    private int age;
    //getter setter方法

    /**
     *  重写clone()方法
     * @return
     */
    @Override
    public Object clone() {
        //浅拷贝
        try {
            // 直接调用父类的clone()方法
            return super.clone();
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }

    @Override
    public String toString() {
        return "[Student: " + this.hashCode() + ",subject:" + subject + ",name:" + name + ",age:" + age + "]";
    }
}

测试类

public class Client{
    public static void main(String[] args) {
        Subject subject = new Subject();
        subject.setName("xxxxxx");

        Student s = new Student();
        s.setSubject(subject);
        s.setAge(20);
        s.setName("sssss");

        Object clone = s.clone();

        System.out.println(s);
        System.out.println(clone);
    }
}

结果:

subject的内部地址一样,所以clone后的对象和原来对象共享subject对象

Student{subject=Subject@7506e922, name='sssss', age=20}
Student{subject=Subject@7506e922, name='sssss', age=20}

深拷贝

深拷贝介绍

深拷贝,在拷贝引用类型成员变量时,为引用类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝。

深拷贝特点
  1. 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)。
  2. 对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。
  3. 对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法,进而实现了对象的串行层层拷贝。
  4. 深拷贝相比于浅拷贝速度较慢并且花销较大。

图示:

深拷贝图示.png
深拷贝的实现

示例1

public class Subject implements Cloneable {

    private String name;
    //getter setter

    @Override
    protected Object clone() throws CloneNotSupportedException {
        //Subject 如果也有引用类型的成员属性,也应该和 Student 类一样实现
        return super.clone();
    }
}

public class Student implements Cloneable {

    //引用类型
    private Subject subject;
    //基础数据类型
    private String name;
    private int age;

    //getter setter

    /**
     *  重写clone()方法
     * @return
     */
    @Override
    public Object clone() {
        //深拷贝
        try {
            // 直接调用父类的clone()方法
            Student student = (Student) super.clone();
            student.subject = (Subject) subject.clone();
            return student;
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }

    @Override
    public String toString() {
        return "[Student: " + this.hashCode() + ",subject:" + subject + ",name:" + name + ",age:" + age + "]";
    }
}

示例2

public class Thing implements Cloneable{
     //定义一个私有变量
     private ArrayList<String> arrayList = new ArrayList<String>();
     @Override
     public Thing clone(){
             Thing thing=null;
             try {
                    thing = (Thing)super.clone();
                    thing.arrayList = (ArrayList&lt;String&gt;)this.arrayList.clone();
             } catch (CloneNotSupportedException e) {
                    e.printStackTrace();
             }
             return thing;
     }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容