天天看點

設計模式——原型模式

原型模式

用原型執行個體指定建立對象的種類,并通過複制這些原型建立新的對象。

原型模式是從一個對象出發得到一個和自己有相同狀态的新對象的成熟模式,該模式的關鍵是将一個對象定義為原型,并為其提供複制自己的方法。

UML類圖

原型模式的結構中包括兩種角色:

抽象原型(Prototype):一個接口,負責定義對象複制自身的方法。

具體原型(Concrete Prototype):實行prototype接口的類,具體

要了解原型原型模式必須先了解

Java

裡的淺複制和深複制,有的地方,複制也叫做克隆。因為Java提供了clone()方法來實作對象的克隆,是以Prototype模式的實作變得很簡單。

淺克隆:被克隆對象的成員變量是對象的話,那麼clone()方法僅僅複制了目前對象所擁有的對象的引用,并沒有複制這個對象所擁有的變量,這就使clone()方法傳回的新對象和目前對象擁有一個相同的對象,未能實作完全意義的複制。

深克隆:被克隆對象的所有變量都含有與原來的對象相同的值,但它所有的對其他對象的引用不再是原有的,而這是指向被複制過的新對象。換言之,深複制把要複制的對象的所有引用的對象都複制了一遍,這種叫做間接複制。

java.lang包中的Object類提供了一個權限是protecd用于複制對象的clone()方法。為了能讓一個對象使用clone()方法,建立該對象的類可以重寫clone()方法,并将通路權限提高為public,為了能夠使用被覆寫的clone()方法,隻需在重寫的clone()方法中使用關鍵字super調用Object類的clone()方法;也可以在建立對象的類中新定義一個複制對象的方法,将通路權限定義為public,并在該方法中調用Object類的clone()方法。另外,當調用Object類中的clone()方法時,JVM将會逐個複制該對象的成員變量,然後建立一個新的對象傳回,是以JVM要求調用clone()方法的對象必須實行Cloneable接口。

舉例

public class User implements Cloneable {
    String name;
    Info info;

    public User(String name, Info info) {
        this.name = name;
        this.info = info;
    }

    /**
     * 重寫clone()方法
     *
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    public java.lang.Object clone() throws CloneNotSupportedException {
        User object = (User) super.clone();
        object.info = (Info) info.customClone();//對Info進行複制
        return object;
    }
}


class Info implements Cloneable {
    String address;

    public Info(String address) {
        this.address = address;
    }

    /**
     * 自定義一個複制對象的方法
     *
     * @return
     */
    public Object customClone() {
        Object object = null;
        try {
            object = clone();//調用Object類的clone()方法
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return object;
    }
}
//使用
public class Test {
    public static void main(String[] args) {
        try {
            User user=new User("張三",new Info("beijing"));
            User userCopy= (User) user.clone();
            System.out.println("user對象中的資料:"+user.name+","+user.info.address);
            System.out.println("userCopy對象中的資料:"+userCopy.name+","+userCopy.info.address);
            System.out.println("修改userCopy對象中的資料");
            userCopy.name="李四";
            userCopy.info.address="shanghai";
            System.out.println("user對象中的資料:"+user.name+","+user.info.address);
            System.out.println("userCopy對象中的資料:"+userCopy.name+","+userCopy.info.address);

        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

    }
}           

使用場景

  • 建立新對象成本較大(如初始化需要占用較長的時間,占用太多的CPU資源或網絡資源),新的對象可以通過原型模式對已有對象進行複制來獲得,如果是相似對象,則可以對其成員變量稍作修改。
  • 如果系統要儲存對象的狀态,而對象的狀态變化很小,或者對象本身占用記憶體較少時,可以使用原型模式配合備忘錄模式來實作。
  • 需要避免使用分層次的工廠類來建立分層次的對象,并且類的執行個體對象隻有一個或很少的幾個組合狀态,通過複制原型對象得到新執行個體可能比使用構造函數建立一個新執行個體更加友善。

注意事項

使用原型模式複制對象不會調用類的構造方法

因為對象的複制是通過調用Object類的clone方法來完成的,它直接在記憶體中複制資料,是以不會調用到類的構造方法。不但構造方法中的代碼不會執行,甚至連通路權限都對原型模式無效。還記得單例模式嗎?單例模式中,隻要将構造方法的通路權限設定為private型,就可以實作單例。但是clone方法直接無視構造方法的權限,是以,單例模式與原型模式是沖突的,在使用時要特别注意。

Serializable接口和克隆對象

相對于clone()方法,Java又提供了一種較簡單的解決方案,這個方案就是使用Serializable接口和對象流來複制對象。如果希望得到對象object的複制品,必須保證該對象是序列化的,即建立object對象的類必須實作Serializable接口。Serializable接口中的方法對程式是不可見的,是以實作該接口的類不需要實作額外的方法。

為了得到object的複制品,首先需要将object寫入ObjectOutputStream流中,當把一個序列化的對象寫入ObjectOutputStream輸出流時,JVM就會實作Serializable接口中的方法,将一定格式的文本——對象的序列化資訊,寫入ObjectOutputStream輸出流的目的地。然後使用ObjectInputStream對象輸入流從ObjectOutputStream輸出流的目的地讀取對象,這時ObjectInputStream對象流就讀回object對象的序列化資訊,并根據序列化資訊建立一個新的對象,這個新對象就是object對象的一個複制品。

需要注意的是,使用對象流把一個對象寫入檔案時不僅要保證該對象是序列化的,而且該對象的成員變量也是序列化的。

//抽象原型
public interface Prototype {
    Object cloneSelf() throws CloneNotSupportedException;
}
//具體原型
public class Student implements Prototype, Serializable {
    String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public Object cloneSelf() throws CloneNotSupportedException {
        Object object = null;
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(this);
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            object = objectInputStream.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return object;
    }
}
//模式使用
public class PrototypeTest {
    public static void main(String[] args) {
        try {
            Student student=new Student();
            student.setName("張三");
            Student studentCopy= (Student) student.cloneSelf();
            System.out.println("student對象中的資料:"+student.name);
            System.out.println("studentCopy對象中的資料:"+studentCopy.name);
            System.out.println("修改studentCopy對象中的資料");
            studentCopy.setName("李四");
            System.out.println("student對象中的資料:"+student.name);
            System.out.println("studentCopy對象中的資料:"+studentCopy.name);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}           

總結

原型模式優點如下:

  • 當建立新的對象執行個體較為複雜時,可以簡化對象建立過程,通過複制一個已有執行個體可以提高執行個體的建立效率。
  • 擴充性較好,原型模式中提供了抽象原型類,在用戶端可以針對抽象原型類進行程式設計。
  • 原型模式提供了簡化的建立結構,工廠方法模式常常需要有一個與産品類等級結構相同的工廠等級結構,而原型模式就不需要這樣,原型模式中産品的複制是通過封裝在原型類中的克隆方法實作的,無須專門的工廠類來建立産品。
  • 可以使用深克隆的方式儲存對象的狀态,使用原型模式将對象複制一份并将其狀态儲存起來,以便在需要的時候使用(如恢複到某一曆史狀态),可輔助實作撤銷操作。

原型模式缺點如下:

  • 需要為每一個類配備一個克隆方法,而且該克隆方法位于一個類的内部,當對已有的類進行改造時違背“開閉原則”。
  • 在實作深克隆時需要編寫較為複雜的代碼,而且當對象之間存在多重的嵌套引用時,為了實作深克隆,每一層對象對應的類都必須支援深克隆比較複雜。

參考:

http://www.cnblogs.com/xudong-bupt/p/3506450.html http://blog.csdn.net/yanbober/article/details/45363525