原型模式——五種創建型模式之一

1.前言


單例模式可以避免重復創建消耗資源的對象,但是卻不得不共用對象。若是對象本身也不讓隨意訪問修改時,怎么辦?通常做法是備份到副本,其它對象操作副本,最后獲取權限合并,類似git上的PR操作。

2.概念


原型模式用原型實例指定創建對象的種類,并通過拷貝這些原型創建新的對象。需要注意的關鍵字是,新的對象,類沒變。Java正好提供了Cloneable接口,它標識的類可以調用Object中實現的clone()方法而不拋出異常,即運行時通知虛擬機可以安全使用clone()方法返回拷貝對象。由于它直接操作內存中的二進制流,當大量操作或操作復雜對象時,性能優勢將會很明顯。

3.場景


動物園中有一只羊,對它進行克隆,產生另外一只完全一樣的羊,分別安排兩位有孩子的管理員照顧。有一天,對克隆羊進行基因操作,觀察變化。

4.寫法

// 1.聲明此類可以被clone
public class Sheep implements Cloneable {
    
    private int age;
    private String sex;
    private Admin admin;

    public Sheep(int age, String sex, Admin admin) {
        super();
        this.age = age;
        this.sex = sex;
        this.admin = admin;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Admin getAdmin() {
        return admin;
    }

    public void setAdmin(Admin admin) {
        this.admin = admin;
    }

    @Override
    public String toString() {
        return "Sheep [age=" + age + ", sex=" + sex + ", admin=" + admin + "]";
    }
    
    // 2.調用Object的clone方法
    public Sheep startClone() {
        Sheep sheep = null;
        try {
            sheep = (Sheep) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return sheep;
    }

}
public class Admin {
    
    private int age;
    private String sex;
    private Child child;
    public Admin(int age, String sex, Child child) {
        super();
        this.age = age;
        this.sex = sex;
        this.child = child;
    }
    
    public void setAge(int age) {
        this.age = age;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public void setChild(Child child) {
        this.child = child;
    }

    @Override
    public String toString() {
        return "Admin [age=" + age + ", sex=" + sex + ", child=" + child + "]";
    }
    
}
public class Child {

}
public class Zoo {

    public static void main(String[] args) {
        Sheep old = new Sheep(2, "雄性", new Admin(25, "女", new Child()));
        System.out.println(old.toString());
        Sheep current = old.startClone();
        System.out.println(current.toString());
        
        // 對克隆羊做處理
        current.setAge(1);
        current.setSex("雌性");
        current.getAdmin().setAge(34);
        current.getAdmin().setSex("男");
        System.out.println(old.toString());
        System.out.println(current.toString());
    }

}

根據上面的代碼,我們知道羊引用了管理員,管理員引用了孩子。當對內存中數據拷貝時,除了基本數據類型(包括封裝類型)及String類型,其它的引用關系將直接傳遞給副本,并不是重新創建一個對象。所以當對克隆羊操作時,年齡和性別直接改變,而對管理員的操作將尋址到內存中對應部分進行修改,導致原型也被修改。孩子與管理員的關系就如同管理員與羊,通過哈希值可以知道,孩子始終就一個,沒有拷貝成功。


light clone.png

上面的錯誤是由于只拷貝了最外層對象的原因,我們稱之為淺拷貝。為了解決這個問題,需要對內部的引用類型進行拷貝(Java中大部分引用類型實現了Cloneable接口,可以方便的拷貝),具體如下:

// 1.聲明此類可以被clone
public class Sheep implements Cloneable {

    // 前面省略
    
    // 2.調用Object的clone方法
    public Sheep startClone() {
        Sheep sheep = null;
        try {
            sheep = (Sheep) super.clone();
            
            // 3.調用Admin的clone方法
            sheep.admin = this.admin.startClone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return sheep;
    }

}
public class Admin implements Cloneable {

    // 前面省略
    
    public Admin startClone() {
        Admin admin = null;
        try {
            admin = (Admin) super.clone();
            admin.child = this.child.startClone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return admin;
    }
    
}
public class Child implements Cloneable {

    public Child startClone() {
        Child child = null;
        try {
            child = (Child) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return child;
    }
    
}

通過日志的打印,發現這種方式(深拷貝)起作用了。由1、2行可以知道,拷貝時引用類型已經重新創建了對象。由3、4行可以知道,修改其中一個對象不會再改變另一個了。


deep clone.png

5.總結


原型模式通過Object的clone()方法實現,由于是內存操作,無視構造方法和訪問權限,直接獲取新的對象。但對于引用類型,需使用深拷貝,其它淺拷貝即可。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容