原型模式:深複製、淺複製,一文了解原型模式

一、前言

看了上一期的單例模式之後,是不是可以應對一些簡單的面試題目了,我相信你可以的。第一稿的原型模式我感覺是比較好完成了,書籍包括視頻上面,都花了很長的篇幅,寫上一篇感覺不算費力。這一篇還真是不好寫,我先從我覺得較簡單的說起吧,這一篇說原型模式,然後說說淺複製和深複製的區別,值類型和引用類型…開講啦

二、原型模式的運用場景和解釋

看了《大話設計模式》和一些博客,他們都喜歡用簡歷這個例子來說明原型模式。在投簡歷的時候我們是寫好一份簡歷,然後複製瘋狂投簡歷。當然我們也對簡歷進行一些簡單崗位薪資方面的修改,這個時候不需要修改基本信息,這時就用到了設計模式了。每次New一個對象,都需要執行一次構造函數,如果構造函數的執行時間很長,那麼多次執行這個初始化的操作不就很浪費時間呀。所以在大部分信息不變的情況下,克隆是最好的辦法。這即隱藏了對象創建的細節,又對性能是大大的提高了。

原型模式不用重新的初始化對象,而是動態地獲得對象運行時的狀態。

三、先看看這幾個簡單的程序

每次新建一份簡歷我都new一下

//簡歷類

package prototype;

/**
 * 原型模式:用原型實例指定創建對象的總類,並且通過拷貝這些原型創建新的對象
 * */

public class Resume {
    private String name;
    private String sex;
    private String age;
    private String timeArea;
    private String company;
    public Resume(String name){
        this.name = name;
    }
    //設置個人信息
    public void SetPersonalInfo(String sex,String age){
        this.sex = sex;
        this.age = age;
    }
    //設置工作經歷
    public void SetWorkExperience(String timeArea,String company){
        this.timeArea = timeArea;
        this.company = company;
    }
}

//主函數

package prototype;

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Resume a = new Resume("大鳥");
        a.SetPersonalInfo("男","30");
        a.SetWorkExperience("2019-2020","xx公司");

        Resume b = new Resume("大鳥");
        b.SetPersonalInfo("男","31");
        b.SetWorkExperience("2019-2020","xx公司");

        Resume c = new Resume("大鳥");
        c.SetPersonalInfo("男","32");
        c.SetWorkExperience("2019-2020","xx公司");

        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
	}
}

//OutPut

prototype.Resume@74a14482
prototype.Resume@1540e19d
prototype.Resume@677327b6

我們新建了3份簡歷,簡歷有一些不同,選擇new了3次,等到了3份簡歷。

直接引用不復制了(錯誤)

//簡歷類

package prototype;

/**
 * 原型模式:用原型實例指定創建對象的總類,並且通過拷貝這些原型創建新的對象
 * */

public class Resume {
    private String name;
    private String sex;
    private String age;
    private String timeArea;
    private String company;
    public Resume(String name){
        this.name = name;
    }
    //設置個人信息
    public void SetPersonalInfo(String sex,String age){
        this.sex = sex;
        this.age = age;
    }
    //設置工作經歷
    public void SetWorkExperience(String timeArea,String company){
        this.timeArea = timeArea;
        this.company = company;
    }
}

//主函數

package prototype;

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Resume a = new Resume("大鳥");
        a.SetPersonalInfo("男","30");
        a.SetWorkExperience("2019-2020","xx公司");
        Resume b = a;
        Resume c = a;

        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
	}
}

//OutPut

prototype.Resume@74a14482
prototype.Resume@74a14482
prototype.Resume@74a14482

這是a、b、c都是同一個對象,這其實違背了複製的想法,是錯誤的。簡單理解這是b和c的內容是:簡歷在a處。

不想複製的時候new對象了

//簡歷類

package prototype;

/**
 * 原型模式:用原型實例指定創建對象的總類,並且通過拷貝這些原型創建新的對象
 * */

public class Resume2 implements Cloneable{
    private String name;
    private String sex;
    private String age;
    private String timeArea;
    private String company;
    public Resume2(String name){
        this.name = name;
    }
    //設置個人信息
    public void SetPersonalInfo(String sex,String age){
        this.sex = sex;
        this.age = age;
    }
    //設置工作經歷
    public void SetWorkExperience(String timeArea,String company){
        this.timeArea = timeArea;
        this.company = company;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

//主函數

package prototype;

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Resume2 a = new Resume2("大鳥");
        a.SetPersonalInfo("男","30");
        a.SetWorkExperience("2019-2020","xx公司");

        Resume2 b = (Resume2) a.clone();
        Resume2 c = (Resume2) a.clone();

        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
    }
}

//OutPut

prototype.Resume2@74a14482
prototype.Resume2@1540e19d
prototype.Resume2@677327b6

可以看到這種clone的方式和new相比起來還是很簡便的,只需要一句就行。如果說你需要初始化100個對象,會花上很多的時間,那麼這一句是很好的選擇。

對副本進行簡單的修改

//簡歷類

package prototype;

/**
 * 原型模式:用原型實例指定創建對象的總類,並且通過拷貝這些原型創建新的對象
 * */

public class Resume2 implements Cloneable{
    private String name;
    private String sex;
    private String age;
    private String timeArea;
    private String company;
    public Resume2(String name){
        this.name = name;
    }
    //設置個人信息
    public void SetPersonalInfo(String sex,String age){
        this.sex = sex;
        this.age = age;
    }
    //設置工作經歷
    public void SetWorkExperience(String timeArea,String company){
        this.timeArea = timeArea;
        this.company = company;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "Resume2{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", age='" + age + '\'' +
                ", timeArea='" + timeArea + '\'' +
                ", company='" + company + '\'' +
                '}';
    }
}

//主函數

package prototype;

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Resume2 a = new Resume2("大鳥");
        a.SetPersonalInfo("男","30");
        a.SetWorkExperience("2019-2020","xx公司");

        Resume2 b = (Resume2) a.clone();
        b.SetWorkExperience("2018-2019","yy公司");
        Resume2 c = (Resume2) a.clone();
        c.SetWorkExperience("2018-2019","zz公司");
        System.out.println("a= " + a.toString());
        System.out.println("b= " + b.toString());
        System.out.println("c= " + c.toString());
    }
}

//OutPut

a= Resume2{name='大鳥', sex='男', age='30', timeArea='2019-2020', company='xx公司'}
b= Resume2{name='大鳥', sex='男', age='30', timeArea='2018-2019', company='yy公司'}
c= Resume2{name='大鳥', sex='男', age='30', timeArea='2018-2019', company='zz公司'}

克隆之後,我們可以對簡歷進行下一步的修改,複製也想做自己。

引用類型就不能做自己麼

//簡歷類

package prototype;

/**
 * 原型模式:用原型實例指定創建對象的總類,並且通過拷貝這些原型創建新的對象
 * */

public class Resume3 implements Cloneable{
    private String name;
    private String sex;
    private String age;

    private WorkExperience workExperience;

    public Resume3(String name){
        this.name = name;
        this.workExperience = new WorkExperience();
    }
    //設置個人信息
    public void SetPersonalInfo(String sex,String age){
        this.sex = sex;
        this.age = age;
    }
    //設置工作經歷
    public void SetWorkExperience(String timeArea,String company){
        this.workExperience.setCompany(company);
        this.workExperience.setTimeArea(timeArea);
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "Resume3{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", age='" + age + '\'' +
                ", workExperience=" + workExperience.getCompany() +
                ",  " + workExperience.getTimeArea() +
                '}';
    }
}

//工作經歷類

package prototype;

public class WorkExperience {
    private String timeArea;
    private String company;

    public String getTimeArea() {
        return timeArea;
    }

    public void setTimeArea(String timeArea) {
        this.timeArea = timeArea;
    }

    public String getCompany() {
        return company;
    }

    public void setCompany(String company) {
        this.company = company;
    }
}

//主函數


package prototype;

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Resume3 a = new Resume3("大鳥");
        a.SetWorkExperience("2019-2020","xx公司");
        a.SetPersonalInfo("男","30");
        
        Resume3 b = (Resume3) a.clone();
        b.SetWorkExperience("2019-2020","yy公司");
        b.SetPersonalInfo("男","31");
        
        Resume3 c = (Resume3) a.clone();
        c.SetWorkExperience("2019-2020","zz公司");
        c.SetPersonalInfo("男","32");
        
        System.out.println("a= " + a.toString());
        System.out.println("b= " + b.toString());
        System.out.println("c= " + c.toString());
    }
}

//OutPut

a= Resume3{name='大鳥', sex='男', age='30', workExperience=zz公司,  2019-2020}
b= Resume3{name='大鳥', sex='男', age='31', workExperience=zz公司,  2019-2020}
c= Resume3{name='大鳥', sex='男', age='32', workExperience=zz公司,  2019-2020}

我們本來是想對3個不同的對象分別個性化他們的workExperience,但是好像程序並不盡人意。這就需要引出一個知識點:淺複製和深複製
淺複製:被複制的所有變量都含有原來的對象相同的值,而所有的對其他對象的引用都仍然指向原來的對象。
深複製:把引用對象的變量指向複製的新對象,而不是原有的被引用的對象。

還有另一個知識點那就是:值類型和引用類型。當我們clone的時候,創建當前對象的淺表副本,方法是創建一個新的對象,然後將當前對象的非靜態字段複製到該新對象。

如果字段是值類型,則對該字段執行逐位複製。如果字段是引用類型,則複製引用但不會複製引用的對象

引用類型的變化

//簡歷類

package prototype;

/**
 * 原型模式:用原型實例指定創建對象的總類,並且通過拷貝這些原型創建新的對象
 * */

public class Resume4 implements Cloneable{
    private String name;
    private String sex;
    private String age;

    private WorkExperienceImprove workExperience;

    public Resume4(String name){
        this.name = name;
        this.workExperience = new WorkExperienceImprove();
    }
    public Resume4(WorkExperienceImprove workExperience) throws CloneNotSupportedException {
        this.workExperience = (WorkExperienceImprove) workExperience.clone();
    }
    //設置個人信息
    public void SetPersonalInfo(String sex,String age){
        this.sex = sex;
        this.age = age;
    }
    //設置工作經歷
    public void SetWorkExperience(String timeArea,String company){
        this.workExperience.setCompany(company);
        this.workExperience.setTimeArea(timeArea);
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Resume4 resume4 = new Resume4(this.workExperience);
        resume4.name = this.name;
        resume4.age = this.age;
        resume4.sex = this.sex;
        return resume4;
    }

    @Override
    public String toString() {
        return "Resume3{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", age='" + age + '\'' +
                ", workExperience=" + workExperience.getCompany() +
                ",  " + workExperience.getTimeArea() +
                '}';
    }
}

//工作經歷類

package prototype;

public class WorkExperienceImprove implements Cloneable {
    private String timeArea;
    private String company;

    public String getTimeArea() {
        return timeArea;
    }

    public void setTimeArea(String timeArea) {
        this.timeArea = timeArea;
    }

    public String getCompany() {
        return company;
    }

    public void setCompany(String company) {
        this.company = company;
    }

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

//主函數

package prototype;

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Resume4 a = new Resume4("大鳥");
        a.SetWorkExperience("2019-2020","xx公司");
        a.SetPersonalInfo("男","30");

        Resume4 b = (Resume4) a.clone();
        b.SetWorkExperience("2019-2020","yy公司");
        b.SetPersonalInfo("男","31");

        Resume4 c = (Resume4) a.clone();
        c.SetWorkExperience("2019-2020","zz公司");
        c.SetPersonalInfo("男","32");

        System.out.println("a= " + a.toString());
        System.out.println("b= " + b.toString());
        System.out.println("c= " + c.toString());
    }
}

//OutPut

a= Resume3{name='大鳥', sex='男', age='30', workExperience=xx公司,  2019-2020}
b= Resume3{name='大鳥', sex='男', age='31', workExperience=yy公司,  2019-2020}
c= Resume3{name='大鳥', sex='男', age='32', workExperience=zz公司,  2019-2020}

這個例子,第一次看,還有點難懂,主要就是我們需要把workExperience也實現Cloneable的clone接口

public Resume4(WorkExperienceImprove workExperience) throws CloneNotSupportedException {
    this.workExperience = (WorkExperienceImprove) workExperience.clone(); 
}
@Override
protected Object clone() throws CloneNotSupportedException {
    Resume4 resume4 = new Resume4(this.workExperience);
    resume4.name = this.name;
    resume4.age = this.age;
    resume4.sex = this.sex;
    return resume4;
}

然後通過重寫clone方法,傳入workExperience的對象,通過構造方法克隆workExperience。

四、淺複製和深複製的區別

在本文的原型模式的最後的兩個栗子當中,大家應該可以體會到淺複製和深複製的區別。
淺複製:被複制的所有變量都含有原來的對象相同的值,而所有的對其他對象的引用都仍然指向原來的對象
深複製:它會把引用對象的變量指向複製的新對象,而不是原有的被引用的對象。

下面引用一下別人的博客,仔細推敲文字還是非常不錯的,如果有用,記得給別人點贊

  1. 淺拷貝可以使用列表自帶的copy()函數(如list.copy()),或者使用copy模塊的copy()函數。深拷貝只能使用copy模塊的deepcopy(),所以使用前要導入:from copy import deepcopy。
  2. 如果拷貝的對象裏的元素只有值,沒有引用,那淺拷貝和深拷貝沒有差別,都會將原有對象複製一份,產生一個新對象,對新對象裏的值進行修改不會影響原有對象,新對象和原對象完全分離開。
  3. 如果拷貝的對象裏的元素包含引用(像一個列表裏儲存着另一個列表,存的就是另一個列表的引用),那淺拷貝和深拷貝是不同的,淺拷貝雖然將原有對象複製一份,但是依然保存的是引用,所以對新對象裏的引用裏的值進行修改,依然會改變原對象裏的列表的值,新對象和原對象完全分離開並沒有完全分離開。而深拷貝則不同,它會將原對象裏的引用也新創建一個,即新建一個列表,然後放的是新列表的引用,這樣就可以將新對象和原對象完全分離開。

原博客:https://blog.csdn.net/qq_34493908/article/details/81560546

五、值類型和引用類型的區別

在本文的栗子中,當我們clone的時候,創建當前對象的淺表副本,方法是創建一個新的對象,然後將當前對象的非靜態字段複製到該新對象。如果字段是值類型,則對該字段執行逐位複製。如果字段是引用類型,則複製引用但不會複製引用的對象

網上找了一些資料,關於值類型和引用類型,他們舉例子都是使用的C#,我也用C#吧。
C#把數據類型分爲兩大類:值類型(數據存放在棧中)和引用類型(數據存放在堆中,但是地址存放在棧中)。值類型包含:簡單類型、枚舉和結構體。引用類型包含:數組,字符串,接口,類,委託。完了!越解釋越亂了,要不我們直接看上面寫的第二個例子吧,Resume b = a;Resume c = a;這就是引用類型,把b和c指向a的地址,其他這時a,b,c都是一樣的。

六、參考資料

書籍:《大話設計模式》 第9章 簡歷複印–原型模式
博客:https://blog.csdn.net/qq_34493908/article/details/81560546

七、關於本系列的解釋

本系列想製作23種設計模式+7種設計原則一系列課程,其目的就是一個簡單的記錄學習的過程。不知道能幫助到多少人,也不知道技術是否會有一定的深度。

製作不易,您的點贊是我最大的動力。

希望我們都能成爲想成爲的人

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