設計模式,創建型模式之原型模式

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章