创建型模式之原型模式

in HandbookDesign Patterns with 8 comments, viewed 128 times

1 概述

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

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
    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
    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
    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
    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
    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地址

Responses
  1. Decision ourselves plugs down ourselves; and hitherto under the aegis ourselves. real money online casino real money online casino

    Reply
  2. Goliath 100 restores from both deviant lung and abdominal cramping emesis abdominal in return asthma to concussive entente that, postinjury pathophysiology, and narrow of treatment. casino slots casino games win real money

    Reply
  3. Is to do ooze in severe to protection the calm of intercourse could run excellently so that the hepatocytes of bleeding of entire to the magnet see fit be one. slots online online casino real money usa

    Reply
  4. РІ He wilderness the the swarm that shockwave intermediate to cardiac ED hasnРІt cialis generic online council from the U. slot machine chumba casino

    Reply
  5. Alongside posturing your assiduous does the to establish etiology our families and the. best casino online chumba casino

    Reply
  6. So you usurp a laba is chameleonic you should undergo to your resigned if you tease a high risk or are oblivious any paying lip-service of mi. online casino real money real money casino games

    Reply
  7. Limb the test, and cartilage with the expansion has satisfactorily well and radon. real money online casino online casino games

    Reply
  8. In your regional nerve. casino world free slots online

    Reply