专栏首页Java进阶指南设计模式之原型模式

设计模式之原型模式

我们在创建对象时,通常是通过new关键字来创建的。但是,思考一下,如果当前类的构造函数很复杂,每次new对象时都会消耗非常多的资源,这样肯定是不行的,耗时又费力。

那有没有什么办法解决这种问题呢?当然有,原型模式就可以解决这个痛点。

原型模式非常好理解,就是类的实例对象可以克隆自身,产生新的实例对象,这样就无需用new来创建。想一下,齐天大圣孙悟空是不是拔一根汗毛,就复制出了很多个一模一样的孙悟空,道理是一样的。(新的对象和原对象,内容相同,但是内存地址不同,因为是不同的对象。)

那在Java中,我们怎么实现原型模式呢?非常简单,只需要原型对象实现Cloneable接口就可以了,看代码:(学生对象的复制,学生对象中包含他所学的学科类的对象)

//学科类
public class Subject {
    private String name;
    private String content;

    public String getName() {
        return name;
    }

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

    public String getContent() {
        return content;
    }

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

    public Subject(String name, String content) {
        this.name = name;
        this.content = content;
    }

    public Subject() {
    }

    @Override
    public String toString() {
        return "Subject{" +
                "name='" + name + '\'' +
                ", content='" + content + '\'' +
                '}';
    }
}
//学生类
public class Student implements Cloneable {
    private int age;
    private String name;
    private Subject subject;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Subject getSubject() {
        return subject;
    }

    public void setSubject(Subject subject) {
        this.subject = subject;
    }

    public Student(int age, String name, Subject subject) {
        this.age = age;
        this.name = name;
        this.subject = subject;
    }

    public Student() {
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", subject=" + subject +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        //实现Cloneable接口,调用父类Object的clone方法来实现对象的拷贝
        return super.clone();
    }
}
public class ProTest {
    public static void main(String[] args) throws Exception {
        Student s1 = new Student(18,"张三",new Subject("语文","这是语文书"));
        //通过调用s1对象的clone方法,即可创建一个新的对象s2
        Student s2 = (Student)s1.clone();

        System.out.println(s1);
        System.out.println(s2);

        s2.setAge(20);
        s2.setName("李四");
        s2.getSubject().setName("数学").setContent("这是数学书");

        System.out.println("=======");
        System.out.println(s1);
        System.out.println(s2);

    }
}

打印结果如下:

Student{age=18, name='张三', subject=Subject{name='语文', content='这是语文书'}}
Student{age=18, name='张三', subject=Subject{name='语文', content='这是语文书'}}
========
Student{age=18, name='张三', subject=Subject{name='数学', content='这是数学书'}}
Student{age=20, name='李四', subject=Subject{name='数学', content='这是数学书'}}

可以发现,s2对象修改的年龄和姓名对原对象s1没有任何影响,但是subject对象修改之后,原对象s1中的subject对象内容也被更改了,这是怎么回事呢?其实,这是因为我们使用的是浅拷贝。(Object对象的clone方法本身就是浅拷贝)

浅拷贝:对值类型的成员变量进行值的复制,对引用类型的成员变量只进行引用的复制,而不复制引用所指向的对象。(嗯?这句话怎么听起来这么熟悉,看下这个:为什么大家都说Java中只有值传递)此时,新对象里边的引用类型变量相当于原对象的引用类型变量的副本,他们指向的是同一个对象。

因此,修改了s2对象的subject对象的内容,原对象s1的subject对象内容也会跟着改变。那如果,我不想让原对象的引用类型变量内容发生改变,应该怎么做呢?

这就要用到深拷贝了,即把引用类型变量的内容也拷贝一份,这样他们就互不影响了。一般有两种方式来实现深拷贝:一种是让需要拷贝的引用类型也实现Cloneable接口,然后重写clone方法;另一种是利用序列化。

clone方式:

还是以上边的例子来说。首先需要修改Subject类让它实现Cloneable接口,重写clone方法。然后修改Student类的clone方法,把所有引用类型的变量手动拷贝一下。

public class Subject implements Cloneable{
    private String name;
    private String content;

    public String getName() {
        return name;
    }

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

    public String getContent() {
        return content;
    }

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

    public Subject(String name, String content) {
        this.name = name;
        this.content = content;
    }

    public Subject() {
    }

    @Override
    public String toString() {
        return "Subject{" +
                "name='" + name + '\'' +
                ", content='" + content + '\'' +
                '}';
    }

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

public class Student implements Cloneable {
    private int age;
    private String name;
    private Subject subject;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Subject getSubject() {
        return subject;
    }

    public void setSubject(Subject subject) {
        this.subject = subject;
    }

    public Student(int age, String name, Subject subject) {
        this.age = age;
        this.name = name;
        this.subject = subject;
    }

    public Student() {
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", subject=" + subject +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Student cloneStu = (Student)super.clone();
        //手动拷贝subject对象,然后赋值给student克隆的新对象
        cloneStu.setSubject((Subject) this.subject.clone());
        return cloneStu;
    }
}

再次运行测试类,会发现修改新对象的引用类型变量已经无法影响原对象的引用类型变量了。

Student{age=18, name='张三', subject=Subject{name='语文', content='这是语文书'}}
Student{age=18, name='张三', subject=Subject{name='语文', content='这是语文书'}}
========
Student{age=18, name='张三', subject=Subject{name='语文', content='这是语文书'}}
Student{age=20, name='李四', subject=Subject{name='数学', content='这是数学书'}}

序列化方式

可以发现,用clone的方式实现起来是非常简单的。但是,考虑如果对象中的引用类型变量很多的时候,这种方式就不太方便 了,因为你要把所有的引用类型都手动clone一遍。另外,如果引用类型又嵌套了多层引用类型,那将是一场灾难。这时,就可以考虑用序列化方式。

系列化方式是通过把对象序列化成二进制流,放到内存中,然后再反序列化成为新的Java对象,这样就可以保证新对象和原对象的互相独立了。

这种方式,需要所有类都实现Serializable接口。Student类只需要添加一个系列化和反序列化的方法就可以了。

public class Subject implements Serializable {
    private String name;
    private String content;

    public String getName() {
        return name;
    }

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

    public String getContent() {
        return content;
    }

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

    public Subject(String name, String content) {
        this.name = name;
        this.content = content;
    }

    public Subject() {
    }

    @Override
    public String toString() {
        return "Subject{" +
                "name='" + name + '\'' +
                ", content='" + content + '\'' +
                '}';
    }
}
public class Student implements Serializable {
    private int age;
    private String name;
    private Subject subject;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Subject getSubject() {
        return subject;
    }

    public void setSubject(Subject subject) {
        this.subject = subject;
    }

    public Student(int age, String name, Subject subject) {
        this.age = age;
        this.name = name;
        this.subject = subject;
    }

    public Student() {
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", subject=" + subject +
                '}';
    }


    //深克隆
    public Object deepClone() throws IOException, ClassNotFoundException{
        //把对象写入到流中
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);
        //从流中读取
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return ois.readObject();
    }
}
public class ProTest {
    public static void main(String[] args) throws Exception {
        Student s1 = new Student(18,"张三",new Subject("语文","这是语文书"));
        Student s2 = (Student)s1.deepClone();

        System.out.println(s1);
        System.out.println(s2);

        s2.setAge(20);
        s2.setName("李四");
        s2.getSubject().setName("数学").setContent("这是数学书");

        System.out.println("========");
        System.out.println(s1);
        System.out.println(s2);

    }
}

可以看到,在测试类中通过调用deepClone自定义的深克隆方法即可。这种方式适合引用类型或者子嵌套特别多的情况,每个类只需要实现Serializable接口即可,Student的deepClone方法也不需要再改动。

需要注意,这种方式的深拷贝,类中引用类型变量不能用 transient 修饰。(不懂的,自行搜索transient关键字,简单说就是用transient修饰的变量默认不会被序列化)

本文分享自微信公众号 - 烟雨星空(mistyskys),作者:烟雨星空

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-01-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 设计模式之--原型模式

    原型模式的核心是实现Cloneable接口,此接口为JDK提供的一个标识接口,只有实现了此接口的类才能被拷贝。 原型模式的通用类图如下;

    代码改变世界-coding
  • 设计模式之原型模式

    爱撒谎的男孩
  • 设计模式之 原型模式

    在Java中实现原型模式十分简单,只需要实现Cloneable接口并重写clone()方法就可以了

    tanoak
  • 设计模式之原型模式

    Specify the kinds of objects to create using a prototypical instance, and create...

    beginor
  • go设计模式之原型模式

    原型在IT领域常被提及,那么什么是原型?就产品设计来举例吧,在产品开发中,产品经理需要根据业务,画出一个产品原型图,然后设计,根据产品原型图画出设计图,前端工程...

    暮雨
  • 设计模式之原型模式(Prototype)

    我们知道,一个类的定义中包括属性和方法。属性用于表示对象的状态,方法用于表示对象所具有的行为。其中,属性既可以是Java中基本数据类型,也可以是引用类型。Jav...

    用户1205080
  • PHP设计模式之原型模式

    原型模式其实更形象的来说应该叫克隆模式。它主要的行为是对对象进行克隆,但是又把被克隆的对象称之为最初的原型,于是,这个模式就这样被命名了。说真的,从使用方式来看...

    硬核项目经理
  • dart设计模式之原型模式

    原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

    Weaster
  • Gof设计模式之原型模式(三)

    模式定义: 复制现有对象实例来创建一个新的实例 模式用途: 例如做发送邮件服务,发送给所有人的短信内容都是基本相同,只有收件人,收件地址不同,...

    用户1257393
  • 23种设计模式之——原型模式

    原型模式其实就是一个对象在创建另一个可定制的对象,而且不需要指定任何创建的细节。Java提供了Coneable接口,其中有一个唯一方法Clone(),实现这个接...

    良月柒
  • 23种设计模式之原型模式

    通俗的讲,就是不再使用new 来创建对象, 而改用 clone 方法来得到新的对象

    烟草的香味
  • 设计模式之原型模式【设计模式】

    用原型实例制定创建对象的种类,并通过拷贝这些原型并创建新的对象,相当于是拷贝一份副本

    简单的程序员
  • 设计模式之原型模式(创建型)

    原型模式(Prototype Pattern):原型模式是提供一个原型接口,提供原型的克隆,创建新的对象,是一种对象创建型模式。

    SmileNicky
  • 图解Java设计模式之原型模式

    现在有一只羊tom,姓名为 : tom,年龄为 :1,颜色为 :白色,请编写程序创建和tom羊属性完全相同的10只羊。

    海仔
  • JS 设计模式之原型模式(创建型)

    原型模式不仅是一种设计模式,它还是一种编程范式(programming paradigm),是 JavaScript 面向对象系统实现的根基。

    Leophen
  • 浅谈JAVA设计模式之——原型模式(Prototype)

    冰河
  • 温故而知新:设计模式之原型模式(Prototype)

    原型模式个人以为最适合的场景:参照现有的某一个对象实例,快速得到多个完整的实例副本。(通常是深拷贝的副本) 深拷贝在c#中实现的最简单方式莫过于通过反序列化得到...

    菩提树下的杨过
  • Android编程设计模式之原型模式实例详解

    本文实例讲述了Android编程设计模式之原型模式。分享给大家供大家参考,具体如下:

    砸漏
  • 设计模式(7)[JS版]-JavaScript设计模式之原型模式如何实现???

    原型模式(prototype)是指用原型实例指向创建对象的种类,并且通过拷贝这些原型创建新的对象。 原型模式不单是一种设计模式,也被称为一种编程泛型。 从设计模...

    AlbertYang

扫码关注云+社区

领取腾讯云代金券