天天看點

手撸設計模式之-原型模式(場景史上最全)原型模式場景介紹原型模式之淺度克隆原型模式之深度克隆

手撸設計模式之-原型模式

  • 原型模式場景介紹
  • 原型模式之淺度克隆
  • 原型模式之深度克隆

原型模式場景介紹

場景一:在我們開發過程中你一定遇見過getter和setter指派操作。這樣雖然看過去很清晰,但是背地裡是一種純屬體力勞動的騷操作。那麼接下來原型模式可以幫我們解決這種大幅度getter和setter的操作。

原型模式(Prototype Pattern)是指原型執行個體指定建立對象的種類,并且通過拷貝這些原型建立新的對象。

原型模式主要适用于以下場景:

1、類初始化消耗資源較多。

2、new 産生的一個對象需要非常繁瑣的過程(資料準備、通路權限等)

3、構造函數比較複雜。

4、循環體中生産大量對象時。

在 Spring 中,原型模式應用得非常廣泛。例如 scope=“prototype”,在我們經常用

的 JSON.parseObject()也是一種原型模式。下面,我們來看看原型模式類結構圖:

手撸設計模式之-原型模式(場景史上最全)原型模式場景介紹原型模式之淺度克隆原型模式之深度克隆

原型模式之淺度克隆

介紹

在淺度克隆中,建立一個新對象,新對象的屬性和原來對象完全相同,對于非基本類型屬性,仍指向原有屬性所指向的對象的記憶體位址。

原型模式之淺度克隆-代碼案例

一個标準的原型模式代碼,應該是這樣設計的。先建立原型 Prototype 接口:
public interface Prototype{
    Prototype clone();
}
           
建立具體需要克隆的對象 ConcretePrototypeA
public class ConcretePrototypeA implements Prototype {

    private int age;
    private String name;
    private List hobbies;

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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

    public List getHobbies() {
        return hobbies;
    }

    public void setHobbies(List hobbies) {
        this.hobbies = hobbies;
    }

    @Override
    public ConcretePrototypeA clone() {
        ConcretePrototypeA concretePrototype = new ConcretePrototypeA();
        concretePrototype.setAge(this.age);
        concretePrototype.setName(this.name);
        concretePrototype.setHobbies(this.hobbies);
        return concretePrototype;
    }

}
           
建立Client對象
public class Client {

    private Prototype prototype;

    public Client(Prototype prototype){
        this.prototype = prototype;
    }
    public Prototype startClone(Prototype concretePrototype){
        return concretePrototype.clone();
    }
}
           
建立測試案例:
public class PrototypeTest {

    public static void main(String[] args) {

        // 建立一個具體的需要克隆的對象
        ConcretePrototypeA concretePrototype = new ConcretePrototypeA();
        // 填充屬性,友善測試
        concretePrototype.setAge(18);
        concretePrototype.setName("prototype");
        List hobbies = new ArrayList<String>();
        concretePrototype.setHobbies(hobbies);
        System.out.println(concretePrototype);

        // 建立Client對象,準備開始克隆
        Client client = new Client(concretePrototype);
        ConcretePrototypeA concretePrototypeClone = (ConcretePrototypeA) client.startClone(concretePrototype);
        System.out.println(concretePrototypeClone);

        System.out.println("克隆對象中的引用類型位址值:" + concretePrototypeClone.getHobbies());
        System.out.println("原對象中的引用類型位址值:" + concretePrototype.getHobbies());
        System.out.println("對象位址比較:" + (concretePrototypeClone.getHobbies() == concretePrototype.getHobbies()));
    }
}

           

輸出結果:

手撸設計模式之-原型模式(場景史上最全)原型模式場景介紹原型模式之淺度克隆原型模式之深度克隆

總結:

從測試結果看出 hobbies 的引用位址是相同的,意味着複制的不是值,而是引用的位址。這 樣 的 話 , 如 果 我 們 修 改 任 意 一 個 對 象 中 的 屬 性 值 , concretePrototype 和concretePrototypeCone 的 hobbies 值都會改變。這就是我們常說的淺克隆。隻是完整複制了值類型資料,沒有指派引用對象。換言之,所有的引用對象仍然指向原來的對象,

顯然不是我們想要的結果。下面我們來看深度克隆繼續改造。

原型模式之深度克隆

介紹

在深度克隆中,建立一個新對象,屬性中引用的其他對象也會被克隆,不再指向原有對象位址。

總之深淺克隆都會在堆中新配置設定一塊區域,差別在于對象屬性引用的對象是否需要進行克隆(遞歸性的)

案例:

我們換一個場景,大家都知道齊天大聖。首先它是一隻猴子,有七十二般變化,把一根毫毛就可以吹出千萬個潑猴,手裡還拿着金箍棒,金箍棒可以變大變小。這就是我們耳熟能詳的原型模式的經典展現。

原型模式之深度克隆-代碼案例

建立原型猴子 Monkey 類:
public class Monkey {
    public int height;
    public int weight;
    public Date birthday;

}
           
建立引用對象金箍棒 Jingubang 類:
public class JinGuBang implements Serializable {
    public float h = 100;
    public float d = 10;

    public void big(){
        this.d *= 2;
        this.h *= 2;
    }
    public void small(){
        this.d /= 2;
        this.h /= 2;
    }
}
           
建立具體的對象齊天大聖 QiTianDaSheng 類:
public class QiTianDaSheng extends Monkey implements Cloneable, Serializable {

    public JinGuBang jinGuBang;

    public QiTianDaSheng() {
        //隻是初始化
        this.birthday = new Date();
        this.jinGuBang = new JinGuBang();
    }

    @Override
    protected Object clone() {
        return this.deepClone();
    }

	//深克隆實作
    public Object deepClone() {
        try {

            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);

            QiTianDaSheng copy = (QiTianDaSheng) ois.readObject();
            copy.birthday = new Date();
            return copy;

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }

	//淺克隆實作
    public QiTianDaSheng shallowClone(QiTianDaSheng target) {

        QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();
        qiTianDaSheng.height = target.height;
        qiTianDaSheng.weight = target.height;

        qiTianDaSheng.jinGuBang = target.jinGuBang;
        qiTianDaSheng.birthday = new Date();
        return qiTianDaSheng;
    }


}

           
測試案例代碼:
public class DeepCloneTest {

    public static void main(String[] args) {

        QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();
        try {
            QiTianDaSheng clone = (QiTianDaSheng)qiTianDaSheng.clone();
            System.out.println("深克隆:" + (qiTianDaSheng.jinGuBang == clone.jinGuBang));
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        QiTianDaSheng q = new QiTianDaSheng();
        QiTianDaSheng n = q.shallowClone(q);
        System.out.println("淺克隆:" + (q.jinGuBang == n.jinGuBang));
    }
}

           

輸出結果:

手撸設計模式之-原型模式(場景史上最全)原型模式場景介紹原型模式之淺度克隆原型模式之深度克隆

注意⚠️>克隆破壞單例模式

如果我們克隆的目标的對象是單例對象,那意味着,深克隆就會破壞單例。實際上防止克隆破壞單例解決思路非常簡單,禁止深克隆便可。要麼在我們的單例類不實作Cloneable 接口;要麼我們重寫 clone()方法,在 clone 方法中傳回單例對象即可,

具體代碼如下:

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

Cloneable 源碼分析

先看我們常用的 ArrayList 就實作了 Cloneable 接口,來看代碼 clone()方法的實作:
public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }
           

繼續閱讀