文档章节

创建型模式之原型模式

Rocky_chi
 Rocky_chi
发布于 03/26 21:15
字数 1497
阅读 17
收藏 0

1 概述

原型模式比较好理解,即以某个对象为原型,创建该对象的副本。我们可以不用知道对象内部的属性以及内部的状态,是迪米特法则的很好体现。

2 原型模式

原型模式一般用在较为复杂对象的创建,并且希望保留对象所持有的状态。Java对这种对象的创建方式也是提供了原生的支持——Object.clone()方法。

public class Object {
    protected native Object clone() throws CloneNotSupportedException;
}

因为Object是所有类的父类,所以Java中所有的类都可以重写clone()方法。当然Object类中的clone()方法也提供了native实现,可以直接通过super.clone()调用,前提是对象需要实现Cloneable接口,否则会报java.lang.CloneNotSupportedException的错误。

3 案例

3.1 浅拷贝

看下面一个例子,用Object类中的clone()方法实现复制:

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Job job = new Job("engineer", "Java coding");
        Person nightfield = new Person(181, 65, job);
        // 复制一个对象
        Person nightfieldCopy = nightfield.clone();

        System.out.format("Height of copied person: %s\n", nightfieldCopy.getHeight());
        System.out.format("Weight of copied person: %s\n", nightfieldCopy.getWeight());
        System.out.format("Job of copied person: %s\n", nightfieldCopy.getJob());

        // 给原对象重新赋值
        nightfield.setHeight(160);
        nightfield.setWeight(80);
        nightfield.getJob().setJobDescription("Python coding");

        System.out.format("Height of copied person: %s\n", nightfieldCopy.getHeight());
        System.out.format("Weight of copied person: %s\n", nightfieldCopy.getWeight());
        System.out.format("Job of copied person: %s\n", nightfieldCopy.getJob());
    }
}

public class Person implements Cloneable {
    private double height;// cm
    private double weight;// kg
    private Job job;

    Person(double height, double weight, Job job) {
        this.height = height;
        this.weight = weight;
        this.job = job;
    }

    public double getHeight() { return height; }

    public void setHeight(double height) { this.height = height; }

    public double getWeight() { return weight; }

    public void setWeight(double weight) { this.weight = weight; }

    public Job getJob() { return job; }

    public void setJob(Job job) { this.job = job; }

    [@Override](https://my.oschina.net/u/1162528)
    protected Person clone() throws CloneNotSupportedException {
        // 直接调用Object的clone()方法
        return (Person)super.clone();
    }
}
public class Job {
    private String jobName;
    private String jobDescription;

    Job(String jobName, String jobDescription) {
        this.jobName = jobName;
        this.jobDescription = jobDescription;
    }

    public void setJobDescription(String description) {this.jobDescription = description }

    public String getJobName() { return jobName; }

    public String getJobDescription() { return jobDescription; }

    [@Override](https://my.oschina.net/u/1162528)
    public String toString() {
        return "Job{" +
                "jobName='" + jobName + '\'' +
                ", jobDescription='" + jobDescription + '\'' +
                '}';
    }
}

输出:

Height of copied person: 181.0
Weight of copied person: 65.0
Job of copied person: Job{jobName='engineer', jobDescription='Java coding'}
Height of copied person: 181.0
Weight of copied person: 65.0
Job of copied person: Job{jobName='engineer', jobDescription='Python coding'}

可以看到,对于基本类型的修改,不会影响副本类;但是对引用对象的修改,会导致副本类也跟着改变。这说明Object类中的clone()方法的默认实现是一个浅拷贝,也就是说副本内部的对象并没有真正复制,而只是复制了引用。 shallow copy

这种机制在很多情况下会导致问题,有没有干净利落的复制方式呢?于是有了深拷贝

3.2 深拷贝

还是刚才的例子,我们通过在clone()方法里手动创建对象并赋值的方式,可以实现深拷贝,下面只给出Person类的代码。

public class Person {
    private double height;// cm
    private double weight;// kg
    private Job job;

    Person(double height, double weight, Job job) {
        this.height = height;
        this.weight = weight;
        this.job = job;
    }

    public double getHeight() { return height; }

    public void setHeight(double height) { this.height = height; }

    public double getWeight() { return weight; }

    public void setWeight(double weight) { this.weight = weight; }

    public Job getJob() { return job; }

    public void setJob(Job job) { this.job = job; }

    [@Override](https://my.oschina.net/u/1162528)
    protected Person clone() {
        Job clonedJob = new Job(job.getJobName(), job.getJobDescription());
        Person person = new Person(height, weight, clonedJob);
        return person;
    }
}

输出:

Height of copied person: 181.0
Weight of copied person: 65.0
Job of copied person: Job{jobName='engineer', jobDescription='Java coding'}
Height of copied person: 181.0
Weight of copied person: 65.0
Job of copied person: Job{jobName='engineer', jobDescription='Java coding'}

对原类的修改,并不影响副本类的值,说明此复制是连同类里面的对象也一起复制了。 deep copy

上面这种深拷贝实现方式,因为需要我们在clone()方法里创建对象并赋值,所以要求我们对类的结构以及属性非常了解。当类比较多或者类的层级很多的时候,会变得很复杂,而且每当该类新增修改属性,都需要修改clone()方法,显然不符合开闭原则

第二种深拷贝的方式,是利用JSON序列化。通过将对象转化成JSON字符串,再转回对象的方式,实现深拷贝,下面只给出关键代码:

public class Person {
    [@Override](https://my.oschina.net/u/1162528)
    protected Person clone() {
        Gson gson = new GsonBuilder().create();
        // 将对象转成JSON String
        String jsonPerson = gson.toJson(this);
        // 将JSON转化回对象
        Person newPerson = gson.fromJson(jsonPerson, this.getClass());
        return newPerson;
    }
}

通过Gson(或者Jackson)包的帮助,我们可以做到对Person对象的深拷贝。这种方式的好处是很通用,我们只依赖于JSON的类库(一般所有工程都会有)。不过因为它涉及到JSON的转化,所以拷贝效率不是很理想。

第三种深拷贝的方法,是通过Serializable接口提供的序列化与反序列化:先将对象转化成二进制流,然后再转回原对象类型。下面只给出关键代码:

public class Person implements Serializable {
    [@Override](https://my.oschina.net/u/1162528)
    protected DeepPerson serializeClone() {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            // 将对象转成二进制流
            oos.writeObject(this);

            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            // 从二进制中读取对象
            return (DeepPerson) ois.readObject();
        } catch (IOException e) {
            logger.error("Error cloning object", e)
            return null;
        } catch (ClassNotFoundException e) {
            logger.error("Error cloning object", e)
            return null;
        }
    }
}

public class Job implements Serializable {
    ...
}

流序列化的方式,效率比JSON序列化高很多,应该说是最理想的,像ApacheSerializationUtils.clone()就是用的这种方法。不过唯一的限制是,目标对象,以及目标对象引用链下的所有对象,都必须实现Serializable接口(如上例中的Person类和Job类),否则无法成功序列化。

4 总结

原型模式是一种比较简单的创建模式,可以实现对象的复制。应用过程中,应当根据实际情况选择对应的最适合的复制方式。

文中例子的github地址

© 著作权归作者所有

Rocky_chi
粉丝 1
博文 40
码字总数 48901
作品 0
杭州
高级程序员
私信 提问
加载中

评论(0)

《设计模式》第二部分 创建型设计模式 第2章 创建型设计模式简介

创建型模式(Creational Pattern)对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚...

Bruceoxl
03/31
0
0
Java程序员从笨鸟到菜鸟之(三十四)大话设计模式(五)创建者模式和原型模式

本文来自:曹胜欢博客专栏。转载请注明出处:http://blog.csdn.net/csh624366188 创建者模式是创建型模式中最负责的一个设计模式了,创建者负责构建一个对象的各个部分,并且完成组装的过程....

长平狐
2012/11/12
260
0
《设计模式》第二部分 创建型设计模式 第6章 原型模式(A:C++实现)

2.1模式动机 在有些系统中,存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂且耗时耗资源,用原型模式生成对象就很高效,就像孙悟空拔下猴毛轻轻一吹就变出很...

Bruceoxl
03/31
0
0
【设计模式】原型模式 Pototype Parttern

前面讲了创建一个对象实例的方法单例模式Singleton Parttern, 创造多个产品的工厂模式(简单工厂模式 Simple Factory Pattern, 工厂方法模式 FactoryMothed Parttern,抽象工厂模式 Abstra...

风之源
2018/08/06
0
0
Java创建型模式的讨论

创建型模式抽象了实例化过程。它们帮助一个系统独立于如何创建、组合和表示它的那些对象。一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化委托给另一个对象。 创建...

tequliapop
2016/01/13
131
0

没有更多内容

加载失败,请刷新页面

加载更多

科技战疫之企业篇:年度最火直播 "云监工"下造医院

“5G信号现在起什么作用?”、“蹭什么热点,现在大部分人都没有5G手机吧?”1月24日,当三大运营商刚刚宣布为武汉火神山和雷神山医院建设5G时,大多数网友都予以质疑。而不到一周后,两个医...

osc_n9lb74k9
24分钟前
31
0
nginx http模块11个阶段驱动模式详解

在nginx调用ngx_http_process_request_headers()方法读取完所有的header数据之后,就调用ngx_http_process_request()方法开始了请求的处理过程,这也就是nginx http模块开始处理请求的11个阶...

爱宝贝丶
24分钟前
28
0
电视广告关不掉?江苏拟规定广告开机可4秒内消失

智能电视开机必须要看一段广告,针对这一饱受消费者诟病的问题。3月10号上午,江苏省消保委发布了《智能电视开机广告技术规范(征求意见稿)》,对开机广告一键关闭功能的设置,提出了明确的标...

osc_c0usoa3v
25分钟前
26
0
B站向武汉等七城隔离观察群众捐赠首批10万个大会员

2月10日午间消息,为丰富抗击疫情地区隔离群众的精神文化生活,哔哩哔哩(以下简称“B站”)联合人民网向抗击新冠肺炎的部分地区,公益捐赠10万个为期一个月的B站大会员。“大会员”是B站推出...

osc_n75mz9au
27分钟前
14
0
基于压力测试工具构建虚拟用户脚本的并发模型

在对于WEB系统进行性能测试时,第一时间想到的是测试出WEB系统能够承受的最大并发虚拟用户(VU)用户数,因为系统的最大VU并发数可以直接反应系统的承载能力。但是人们往往忽略了VU的并发模型...

osc_p0v6j6lt
28分钟前
27
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部