设计模式——原型模式

设计模式中,单例模式应该是大家最为熟悉的了,那如果我们需要对一个对象进行多次复制的话,大家会用什么呢?这就要用到今天要讲的原型模式了。

简介

其定义为:

使用原型实例指定将要创建的对象类型,通过复制这个实例创建新的对象。

具体来说就是,通过给出一个原型对象来指明所创建的对象的类型,然后使用自身实现的克隆接口来复制这个原型对象,该模式就是用这种方式来创建出更多同类型的对象。

这样的好处是:

Object 类的 clone() 方法是一个本地方法,它可以直接操作内存中的二进制流,所以性能相对 new 实例化来说,更加优秀。

一个对象通过 new 实例化创建过程为:

  1. 在内存中开辟一块空间。
  2. 在开辟的内存空间中创建对象。
  3. 调用对象的构造函数进行初始化对象。

而一个对象通过 clone() 创建过程为:

  1. 根据原对象内存大小开辟一块内存空间。
  2. 复制已有对象,克隆对象中所有属性值。

相对 new 来说,clone() 少了调用构造函数。如果构造函数中存在大量属性初始化或大对象,则使用 clone() 的复制对象的方式性能会好一些。

简单例子

让我们通过一个例子来具体了解一下:

/**
 * 实现Cloneable 接口的原型抽象类Prototype
 */
public class Prototype implements Cloneable {
    /**
     * 重写 clone() 方法
     */
    @Override
    public Prototype clone() {
        Prototype prototype = null;
        try {
            prototype = (Prototype) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return prototype;
    }
}

/**
 * 实现原型类
 */
public class ConcretePrototype extends Prototype {
    public void show() {
        System.out.println("原型模式实现类");
    }
}

/**
 * 测试类
 */
public class Client {
    public static void main(String[] args) {
        ConcretePrototype cp = new ConcretePrototype();
        for (int i = 0; i < 10; i++) {
            ConcretePrototype cloneCp = (ConcretePrototype) cp.clone();
            cloneCp.show();
        }
    }
}

当我们实现原型抽象类时,需要注意三点:

  1. 实现 Cloneable 接口:Cloneable 接口与序列化接口的作用类似,它只是告诉虚拟机可以安全地在实现了这个接口的类上使用 clone() 方法。在 JVM 中,只有实现了 Cloneable 接口的类才可以被拷贝,否则会抛出 CloneNotSupportedException 异常。
  2. 重写 Object 类中的 clone() 方法:在 Java 中,所有类的父类都是 Object 类,而 Object 类中有一个 clone() 方法,作用是返回对象的一个拷贝。
  3. 在重写的 clone() 方法中调用 super.clone():默认情况下,类不具备复制对象的能力,需要调用 super.clone() 来实现。

深拷贝与浅拷贝

谈到了拷贝,就不得不说到一个经典的问题:深拷贝与浅拷贝,有的地方也叫深克隆与浅克隆

在上面的原型模式中,在调用 super.clone() 方法之后,首先会检查当前对象所属的类是否支持 clone,也就是看该类是否实现了 Cloneable 接口。

如果支持,则创建当前对象所属类的一个新对象,并对该对象进行初始化,使得新对象的成员变量的值与当前对象的成员变量的值一模一样,但对于其它对象的引用以及 List 等类型的成员属性,则只能复制这些对象的引用了。所以简单调用 super.clone() 这种克隆对象方式,就是一种浅拷贝

为了让大家更加清楚浅拷贝的弊端,举个具体的例子:

Student 类中有一个 Teacher 对象,我们让这两个类都实现 Cloneable 接口:

@Getter
@Setter
public class Student implements Cloneable{

    /**
     * 学生姓名
     */
    private String name;

    /**
     * 学生所属的老师
     */
    private Teacher teacher;

    /**
     * 重写克隆方法,对学生进行克隆
     */
    public Student clone() {
        Student student = null;
        try {
            student = (Student) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return student;
    }
}

@Getter
@Setter
public class Teacher implements Cloneable{

    /**
     * 老师姓名
     */
    private String name;

    /**
     * 重写克隆方法,对老师类进行克隆
     */
    public Teacher clone() {
        Teacher teacher= null;
        try {
            teacher= (Teacher) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return teacher;
    }
}

测试的时候,我们先定义一个学生和一个老师,并让其关联在一起。然后复制之前的学生,生成一个新的学生,修改新学生的老师。

public class Test {
    public static void main(String args[]) {
        // 定义老师1
        Teacher teacher = new Teacher();
        teacher.setName("刘老师");
        // 定义学生1
        Student stu1 = new Student();
        stu1.setName("test1");
        // 老师1和学生1进行关联
        stu1.setTeacher(teacher);

        // 复制学生1,生成学生2
        Student stu2 = stu1.clone();
        stu2.setName("test2");
        // 修改学生2的老师
        stu2.getTeacher().setName("王老师");

        // 查看修改结果
        System.out.println("学生" + stu1.getName() + "的老师是:" + stu1.getTeacher().getName());
        System.out.println("学生" + stu1.getName() + "的老师是:" + stu2.getTeacher().getName());
    }
}

我们想要的结果是:

学生test1的老师是:刘老师
学生test2的老师是:王老师

但实际结果是:

学生test1的老师是:王老师
学生test2的老师是:王老师

观察以上运行结果,我们可以发现:在我们给学生2修改老师的时候,学生1的老师也跟着被修改了。这就是浅拷贝带来的问题。

我们可以通过深拷贝的方式解决这类问题,修改 Student 类的 clone() 方法:

    /**
     * 重写克隆方法,对学生和老师都进行克隆
     */
    public Student clone() {
        Student student = null;
        try {
            student = (Student) super.clone();
            // 克隆 teacher 对象
            Teacher teacher = this.teacher.clone();
            student.setTeacher(teacher);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return student;
    }

此时,我们再次运行 Test 中的 main() 方法,就可以得到我们预想的结果了。

适用场景

在一些重复创建对象的场景下,我们就可以使用原型模式来提高对象的创建性能。例如:循环体内创建对象时,我们就可以考虑用 clone() 的方式来实现。

除此之外,原型模式在开源框架中的应用也非常广泛。例如 Spring 中,@Service 默认都是单例的。用了私有全局变量,若不想影响下次注入或每次上下文获取 bean,就需要用到原型模式,我们可以通过以下注解来实现,@Scope("prototype")。有兴趣的朋友深入了解一下其中的原理。

总结

原型模式,就是针对需要大量复制同一对象的场景,比如用户获取商品、循环体内创建对象等,都是不错的选择,且效率好。

有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。

https://death00.github.io/

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

推荐阅读更多精彩内容