原型模式小結

甜樹果子二號發表於2019-07-10

 

一、原型模式

原型模式就是從一個物件再建立另一個可定製的物件,而且不需要知道任何建立的細節。

二、基本的原型模式

這裡模擬簡歷的建立與複製來說明原型模式的應用。

class Resume implements Cloneable{
    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;
    }

    //列印
    public void display(){
        System.out.println("name sex age:"+name+" "+sex+" "+age);
        System.out.println("work experience"+timeArea+" "+company);
    }

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

public class PrototypePattern {
    public static void main(String[] args) throws CloneNotSupportedException{
        Resume a = new Resume("a的簡歷");
        a.setPersonalInfo("male", "18");
        a.setWorkExperience("2017", "companya");

        Resume b=(Resume) a.clone();
        b.setWorkExperience("2016","companyb");

        Resume c=(Resume) a.clone();
        c.setWorkExperience("2019","companyc");

        a.display();
        b.display();
        c.display();
    }
}

輸出結果:

 

三、原型模式中的淺複製與深複製

上面的Resume類通過實現Cloneable介面才能使用clone方法,進行物件的克隆。

Java中物件的建立通常是通過new來實現的,通過從堆中申請一塊與需要的物件型別對應的記憶體空間的大小,在呼叫構造方法返回物件給引用,

假如需要建立一批屬性值都相同的物件,或許可以通過new一批物件來實現,但是這樣的話,效率未免太低,並不划算。

那麼如果是這樣呢?

class PersonTest{

}

public class Test {
    public static void main(String[] args) {
        PersonTest personTest=new PersonTest();
        PersonTest personTest1=personTest;
        System.out.println(personTest);
        System.out.println(personTest1);
    }
}

輸出結果:

可見personTest和personTest1的地址是一樣的,說明他們兩個其實是指向同一個物件的引用,在這個過程中並沒有進行物件的複製。   

那如果是進行一次克隆呢?像這樣:

class Person implements Cloneable{
    int id;
    String name;
    Education education;

    public Person(int id, String name,Education education) {
        this.id = id;
        this.name = name;
        this.education=education;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public Education getEducation() {
        return education;
    }

    public void setEducation(Education education) {
        this.education = education;
    }

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

class Education{
    String theUniversity;
    String degree;

    public Education(String theUniversity, String degree) {
        this.theUniversity = theUniversity;
        this.degree = degree;
    }

    public String getTheUniversity() {
        return theUniversity;
    }

    public void setTheUniversity(String theUniversity) {
        this.theUniversity = theUniversity;
    }

    public String getDegree() {
        return degree;
    }

    public void setDegree(String degree) {
        this.degree = degree;
    }
}

public class ObjectClone {
    public static void main(String[] args) throws CloneNotSupportedException{
        Education educationA=new Education("清華大學","學士");
        Person personA=new Person(1,"小明",educationA);
        Person personB=(Person) personA.clone();
        System.out.println(personA==personB);
        System.out.println(personA.id==personB.id);
        System.out.println(personA.name==personB.name);
        System.out.println(personA.education==personB.education);
        System.out.println();
        System.out.println(personA);
        System.out.println(personB);
        System.out.println(personA.id);
        System.out.println(personB.id);
        System.out.println(personA.name);
        System.out.println(personB.name);
        System.out.println(personA.education);
        System.out.println(personB.education);
    }
}

 

輸出結果:

可見克隆以後進行了物件的複製,personA與personB的地址是不同的,複製以後兩個物件的id值與name,education都相同,比較結果都為true,乍看上去這好像是預期中的現象。

但是,

name作為String物件,在進行“==”的比較時比較的是物件是否相同,可見這次複製過程中,name的值並沒有複製,

Education的值是一個物件,education的地址也相同,

所以這次物件複製後的情況,為什麼跟上面的例子只傳引用沒有什麼區別啊?

克隆的過程中有幾個問題需要進行思考,

id作為int型資料,int是基本資料型別,複製的過程當中是逐位複製的,

但是對於String型別與其他引用型別,這裡都只是將原物件的引用值拷貝給了新物件的相應欄位。

 

這種就是淺複製,clone()方法進行的就是淺拷貝,這是需要注意的問題。

 

那麼如果需要進行深拷貝,則需要將clone()方法進行覆蓋,並且在clone()方法內把原物件引用的其他物件也拷貝一份。

那麼被引用的物件也需要實現Cloneable介面,並且實現clone()方法。

class Person implements Cloneable{
    int id;
    String name;
    Education education;

    public Person(int id, String name,Education education) {
        this.id = id;
        this.name = name;
        this.education=education;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person person=(Person) super.clone();
        person.education=(Education) education.clone();
        return person;
    }
}

class Education implements Cloneable{
    String theUniversity;
    String degree;

    public Education(String theUniversity, String degree) {
        this.theUniversity = theUniversity;
        this.degree = degree;
    }

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

public class ObjectClone {
    public static void main(String[] args) throws CloneNotSupportedException{
        Education educationA=new Education("清華大學","學士");
        System.out.println(educationA);
        Person personA=new Person(1,"小明",educationA);
        Person personB=(Person) personA.clone();
        System.out.println(personA==personB);
        System.out.println(personA.id==personB.id);
        System.out.println(personA.name==personB.name);
        System.out.println(personA.education==personB.education);
        System.out.println();
        System.out.println(personA);
        System.out.println(personB);
        System.out.println(personA.id);
        System.out.println(personB.id);
        System.out.println(personA.name);
        System.out.println(personB.name);
        System.out.println(personA.education);
        System.out.println(personB.education);
    }
}

輸出結果如下:

程式碼跟上方相比,有一點點改動,但是根據輸出結果來看,這次原物件中的某些欄位的所引用的物件也進行了拷貝了。

 

四、關於clone的總結

由此可見clone()只是淺拷貝,也就是說除非基本型別外,引用型別只是拷貝引用,不會複製物件。

如果需要進行深拷貝,則需要使原物件所引用的物件型別也要繼承Cloneable介面,並且實現clone()方法,同時要注意物件與物件之間存在的巢狀問題,避免拷貝後的兩個物件仍然因為引用的某個物件存在關係。

 

五、關於Cloneable介面

上面提到了Cloneable介面,再對這個東西多瞭解一點,下面這個就是Cloneable介面,可見當中沒有任何方法和屬性。

Cloneable介面與Serializable介面一樣,都是標記型的介面,

實現 Cloneable來表示該物件能被克隆,能使用Object.clone()方法。如果沒有實現 Cloneable的類物件呼叫clone()就會丟擲CloneNotSupportedException。

如果將上面最後一個例項程式碼中Education類實現的Cloneable介面去除,那麼程式執行時會出現異常,

這裡想思考的是標記型介面是如何起作用的呢?

標記介面是沒有任何方法和屬性的介面,僅僅表明實現它的類是屬於一個特定的型別,我們知道實現一個介面的類是可以來代表這個介面型別的。

通常建立標記介面的目的就是主要是:

建立一個公共的父介面,之後可以通過多型對其進行擴充套件,但是Java虛擬機器卻可以根據這個介面的型別選擇相應的事件處理方案,也就是對實現這個標記介面的物件進行這個處理方案。

關於標記介面的作用描述還是不夠清晰,待之後學習的再深入一點再繼續探究吧。

 

相關文章