設計模式(Java語言)- 原型模式

rainple發表於2020-05-05

  原型模式(Prototype Pattern)也有人將原型模式稱為克隆模式,是屬於創造型設計模式,用於建立重複的物件,提供了一種建立物件的最佳方式。原型模式需要實現Cloneable介面,來實現物件的克隆。在實際的應用中,如果應用需要反覆建立相同的物件時,並且建立這個物件需要花費大量時間或者需要訪問許可權,比如需要讀取資料庫,配置檔案等,如果每次建立重複物件都需要讀一次資料庫,那麼這種方式顯然並不是高效的。這時可以考慮使用原型模式來解決,提高效率,此時只需要在建立原型物件時需要讀取一次資料庫或配置檔案等,當後面需要需要建立這個物件時只需要從原型物件克隆一個出來即可。另外,原型模式也解決了構建複雜物件時繁瑣的過程,原型模式不關心物件建立的細節,使用者只需要呼叫克隆的方法就可以建立出一個一摸一樣的物件,簡化建立流程。

  既然原型模式也成為克隆模式,那麼物件複製過程必然用到Java的克隆方法。所以你也需要了解什麼是淺克隆和深克隆。

  淺克隆

  淺克隆複製的是物件基本型別的屬性,對於引用型別的屬性,淺克隆置複製該應用型別的地址,因為克隆物件的被克隆物件的應用型別屬性是同一個記憶體地址,即為同一個物件,所以在修改其中一個物件的該屬性時,另一個物件的改屬性也會被修改,很容易將原型物件屬性修改,這也是在使用原型模式時需要注意的地方。淺克隆在程式碼中的實現也比較簡單,Java語言中本身就已經提供相關的介面和方法了,我們在使用時只需要繼承Cloneable介面,重寫clone方法即可實現物件的淺克隆。程式碼實現如下:

public class Sheep implements Cloneable {

    private String name;
    private Color color = new Color();

    public Sheep() {
    }

    public Sheep(String name, String color) {
        this.name = name;

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

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", color=" + color +
                '}';
    }

    public Color getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color.setColor(color);
    }

    public String getName() {
        return name;
    }

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

  

public class Color implements Cloneable {

    private String color;

    public Color() {
    }

    public Color(String color) {
        this.color = color;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

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

    @Override
    public String toString() {
        return "Color{" +
                "color='" + color + '\'' +
                '}';
    }
}

  測試

public class Test {

public static void main(String[] args) throws CloneNotSupportedException {
Sheep test = new Sheep("test","白色");
System.out.println(test);
Sheep clone = (Sheep) test.clone();
clone.setColor("黑色");
clone.setName("test01");
System.out.println(test);
System.out.println(clone);
}

}

  執行程式時控制檯列印出了:

  Sheep{name='test', color=Color{color='白色'}}

  Sheep{name='test', color=Color{color='黑色'}}

  Sheep{name='test01', color=Color{color='黑色'}}

  很顯然,test物件建立時是白色的,然後用這個物件進行克隆得到 clone 例項,然後將clone 物件的顏色修改成黑色,name修改成test01,最終兩個物件的顏色都變成了黑色,印證了上面說的話,對於引用型別克隆的是物件的記憶體地址。可能會有人好奇,String也是引用型別,為什麼克隆物件修改了name屬性,原型物件卻沒有被修改了?這是因為String是final型別,克隆過程中自然會是兩個不同的記憶體地址。

 

  深克隆

  深克隆和淺克隆的區別在於,深克隆時引用型別屬性複製的是該屬性的值,與原型物件的擁有不同的記憶體地址,即兩個是不同的物件,他們任意一個改屬性值都不會影響到彼此。深克隆的實現方式有兩種,第一種,實現Cloneable介面,重寫clone方法,與淺克隆不同的是多一步將引用型別的變數再呼叫一次改變數的clone方法。不推薦用這種方法實現深克隆,每次修改物件的變數時都需要修改一次clone方法,違反了ocp原則。第二種,利用Java序列化與發序列化來實現,推薦使用這種方式。

  程式碼實現:

public class Color implements Cloneable, Serializable {

    private String color;

    public Color() {
    }

    public Color(String color) {
        this.color = color;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

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

    @Override
    public String toString() {
        return "Color{" +
                "color='" + color + '\'' +
                '}';
    }
}

  

public class Sheep implements Cloneable, Serializable {

    private String name;
    private Color color = new Color();

    public Sheep() {
    }

    public Sheep(String name, String color) {
        this.name = name;

        setColor(color);
    }

    /**
     * 利用序列化與反序列化實現深克隆
     * @return
     */
    public Object deepClone() {
        ByteArrayInputStream bis = null;
        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        try {
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);


            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            return ois.readObject();
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                if (ois != null) {
                    ois.close();
                }
                if (bis != null) {
                    bis.close();
                }
                if (oos != null) {
                    oos.close();
                }
                if (bos != null) {
                    bos.close();
                }
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Sheep clone = (Sheep) super.clone();
        clone.color = (Color) clone.color.clone();
        return clone;
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", color=" + color +
                '}';
    }

    public Color getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color.setColor(color);
    }

    public String getName() {
        return name;
    }

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

  注意,如果使用Java的序列化與反序列化,則改物件需要實現Serializable介面,否則會拋序列化異常。

  

  總結

  1、原型模式有兩種實現方式,第一種利用Object類中的clone方式,重寫Cloneable的clone方法,淺克隆時直接呼叫Object類提供的clone方式即可。深克隆則需要再呼叫需要被克隆的物件的clone方法,當然該物件也必須實現Cloneable介面。第二種方式是利用Java的序列化和反序列化技術,這種方式也有一個缺點是所有需要序列化的變數都必須要實現Serializable介面。

  2、原型模式的優點:提高效率;遮蔽複雜的物件構建過程,簡化程式碼。

  3、原型模式的缺點:

    1)配備克隆方法需要對類的功能進行通盤考慮,這對於全新的類不是很難,但對於已有的類不一定很容易,特別當一個類引用不支援序列化的間接物件,或者引用含有迴圈結構的時候。

    2)必須實現 Cloneable 介面或Serializable介面。

  4、原型模式的應用場景:

    1)資源優化場景。

    2)物件初始化需要大量的資源,包括資料,硬體資源等。

相關文章