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

  原型模式(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)對象初始化需要大量的資源,包括數據,硬體資源等。