設(shè)計(jì)模式--原型模式

目錄

本文的結(jié)構(gòu)如下:

  • 引言
  • 什么是原型模式
  • 淺克隆和深克隆
  • clone()
  • 模式的結(jié)構(gòu)
  • 典型代碼
  • 代碼示例
  • 優(yōu)點(diǎn)和缺點(diǎn)
  • 適用環(huán)境

一、引言

我是一個很幼稚的人,所以經(jīng)常會有很幼稚的想法,比如,有時候上著班我就在想,要是我能夠影分身多好,這樣,我可以讓一號分身陪女朋友和家人,二號分身上班敲代碼,三號分身街頭賣烤串,四號分身被窩玩游戲......

我的可笑想法在當(dāng)下是不現(xiàn)實(shí)的,但在軟件開發(fā)中,卻是非常務(wù)實(shí)的。設(shè)計(jì)模式中有一個模式,可以通過一個原型對象克隆出多個一模一樣的對象,該模式稱之為原型模式。

二、什么是原型模式

在使用原型模式時,我們需要首先創(chuàng)建一個原型對象,再通過復(fù)制這個原型對象來創(chuàng)建更多同類型的對象。一般這個原型對象的實(shí)例化很復(fù)雜,需要消耗很多的硬件資源或者數(shù)據(jù)資源,就像引言中說的“我”,是經(jīng)過20多年的實(shí)例化,消耗了大量的糧食才構(gòu)造成功的。

原型模式(Prototype Pattern):當(dāng)創(chuàng)建給定類的實(shí)例化過程很復(fù)雜或者代價很昂貴時,可以使用原型實(shí)例指定創(chuàng)建對象的種類,并且通過拷貝這些原型創(chuàng)建新的對象。原型模式是一種對象創(chuàng)建型模式。

原型模式的工作原理很簡單:將一個原型對象傳給那個要發(fā)動創(chuàng)建的對象,這個要發(fā)動創(chuàng)建的對象通過請求原型對象拷貝自己來實(shí)現(xiàn)創(chuàng)建過程,拷貝通常是通過克隆方法實(shí)現(xiàn)。原型模式是一種“另類”的創(chuàng)建型模式,創(chuàng)建克隆對象的工廠就是原型類自身,工廠方法由克隆方法來實(shí)現(xiàn)。

三、淺克隆和深克隆

需要注意的是克隆有深克隆和淺克隆之分。

淺克?。?在淺克隆中,如果原型對象的成員變量是值類型,將復(fù)制一份給克隆對象;如果原型對象的成員變量是引用類型,則將引用對象的地址復(fù)制一份給克隆對象,也就是說原型對象和克隆對象的成員變量指向相同的內(nèi)存地址。簡單來說,在淺克隆中,當(dāng)對象被復(fù)制時只復(fù)制它本身和其中包含的值類型的成員變量,而引用類型的成員對象并沒有復(fù)制。

深克?。?在深克隆中,無論原型對象的成員變量是值類型還是引用類型,都將復(fù)制一份給克隆對象,深克隆將原型對象的所有引用對象也復(fù)制一份給克隆對象。簡單來說,在深克隆中,除了對象本身被復(fù)制外,對象所包含的所有成員變量也將復(fù)制。

四、clone()

4.1、clone()方法理解

clone()方法是Object中的一個方法,其源代碼如下:

protected native Object clone() throws CloneNotSupportedException;

可以發(fā)現(xiàn):

  • 第一:Object類的clone()方法是一個native方法,native方法的效率一般來說都是遠(yuǎn)高于Java中的非native方法。這也解釋了為什么要用Object中clone()方法而不是先new一個類,然后把原始對象中的信息復(fù)制到新對象中,雖然這也實(shí)現(xiàn)了clone功能。

JNI是Java Native Interface的 縮寫。從Java 1.1開始,Java Native Interface(JNI)標(biāo)準(zhǔn)成為java平臺的一部分,它允許Java代碼和其他語言寫的代碼進(jìn)行交互。JNI一開始是為了本地已編譯語言,尤其是C和C++而設(shè)計(jì)的,但是它并不妨礙你使用其他語言,只要調(diào)用約定受支持就可以了。使用java與本地已編譯的代碼交互,通常會喪失平臺可移植性。但是,有些情況下這樣做是可以接受的,甚至是必須的,比如,使用一些舊的庫,與硬件、操作系統(tǒng)進(jìn)行交互,或者為了提高程序的性能。JNI標(biāo)準(zhǔn)至少保證本地代碼能工作在任何Java虛擬機(jī)實(shí)現(xiàn)下。

  • 第二:Object類中的clone()方法被protected修飾符修飾。這也意味著如果要應(yīng)用clone()方法,必須繼承Object類,在Java中所有的類是缺省繼承Object類的,也就不用關(guān)心這點(diǎn)了。然后重載clone()方法。還有一點(diǎn)要考慮的是為了讓其它類能調(diào)用這個clone類的clone()方法,重載之后要把clone()方法的屬性設(shè)置為 public。
  • 第三:Object.clone()方法返回一個Object對象。必須進(jìn)行強(qiáng)制類型轉(zhuǎn)換才能得到需要的類型。

4.2、Java中對象的克隆

一般需要四個步驟:

  1. 在子類中實(shí)現(xiàn)Cloneable接口。
  2. 為了獲取對象的一份拷貝,我們可以利用Object類的clone方法。
  3. 在子類中覆蓋clone方法,聲明為public。
  4. 在子類的clone方法中,調(diào)用super.clone()。

Cloneable接口僅僅是一個標(biāo)志接口,而且這個標(biāo)志也僅僅是針對Object類中 clone()方法的,如果clone類沒有實(shí)現(xiàn)Cloneable接口,并調(diào)用了Object的clone()方法(也就是調(diào)用了super.Clone()方法),那么Object的clone()方法就會拋出 CloneNotSupportedException 異常。

4.3、淺克隆實(shí)例

public class Student implements Cloneable{

    private String name;

    private int age;

    private Professor professor;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public Professor getProfessor() {
        return professor;
    }

    public void setProfessor(Professor professor) {
        this.professor = professor;
    }

    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + ", professor="
                + professor + "]";
    }

    public Object clone() throws CloneNotSupportedException{
        return super.clone();
    }
}

public class Professor {

    private String name;

    private int age;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "Professor [name=" + name + ", age=" + age + "]";
    }

}

public class ShadowCopy {

    public static void main(String[] args) {
        Professor p1 = new Professor();
        p1.setName("Professor Zhang");
        p1.setAge(30);

        Student s1 = new Student();
        s1.setName("xiao ming");
        s1.setAge(18);
        s1.setProfessor(p1);

        System.out.println(s1);

        try {
            Student s2 = (Student) s1.clone();
            Professor p2 = s2.getProfessor();
            p2.setName("Professor Li");
            p2.setAge(45);
            s2.setProfessor(p2);
            System.out.println("復(fù)制后的:s1 = " + s1);
            System.out.println("復(fù)制后的:s2 = " + s2);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

    }

}

測試結(jié)果會發(fā)現(xiàn)復(fù)制后打印出來的s1和s2結(jié)果是一樣的,s1和s2的導(dǎo)師都變成了45歲的Professor Li,顯然這并不是期望的結(jié)果,產(chǎn)生這個結(jié)果的原因lone()方法實(shí)現(xiàn)的是淺克隆,對象引用professor只是復(fù)制了其引用,s1和s2仍是指向相同的地址塊。

4.4、深克隆實(shí)例

實(shí)現(xiàn)深克隆,可以在原clone()方法基礎(chǔ)上改進(jìn)一下,如下:

public class Student implements Cloneable{

    private String name;
    private int age;
    private Professor professor;

    public Student(String name, int age, Professor professor){
        this.name = name;
        this.age = age;
        this.professor = professor;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public Professor getProfessor() {
        return professor;
    }

    public void setProfessor(Professor professor) {
        this.professor = professor;
    }

    public Object clone(){
        Student o = null;
        try {
            o = (Student)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        o.professor = (Professor)professor.clone();
        return o;
    }

    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + ", professor="
                + professor + "]";
    }

}

public class Professor implements Cloneable{

    private String name;
    private int age;

    public Professor(String name, int age){
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    @Override
    public Object clone(){
        Object o = name;
        try {
            o= super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return o;
    }

    @Override
    public String toString() {
        return "Professor [name=" + name + ", age=" + age + "]";
    }

}

public class DeepClone {
    public static void main(String[] args) {
        Professor p=new Professor("wangwu",50);
        Student s1=new Student("zhangsan",18, p);
        System.out.println(s1);

        Student s2=(Student)s1.clone();
        s2.getProfessor().setName("maer");
        s2.getProfessor().setAge(40);
        System.out.println("復(fù)制后的:s1 = " + s1);
        System.out.println("復(fù)制后的:s2 = " + s2);
    }
}

也可以利用序列化反序列化來實(shí)現(xiàn)深克隆:

public class Student implements Serializable {

    private String name;
    private int age;
    private Professor professor;

    public Student(String name, int age, Professor professor){
        this.name = name;
        this.age = age;
        this.professor = professor;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public Professor getProfessor() {
        return professor;
    }

    public void setProfessor(Professor professor) {
        this.professor = professor;
    }

    public Object deepClone() throws IOException, ClassNotFoundException {
        //將對象寫到流中
        ByteArrayOutputStream bo=new ByteArrayOutputStream();
        ObjectOutputStream oo=new ObjectOutputStream(bo);
        oo.writeObject(this);
        //從流中讀出來
        ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
        ObjectInputStream oi=new ObjectInputStream(bi);
        return oi.readObject();
    }

    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + ", professor="
                + professor + "]";
    }

}

public class Professor implements Serializable {

    private String name;
    private int age;

    public Professor(String name, int age){
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "Professor [name=" + name + ", age=" + age + "]";
    }

}

public class DeepClone {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Professor p=new Professor("wangwu",50);
        Student s1=new Student("zhangsan",18, p);
        System.out.println(s1);

        Student s2=(Student)s1.deepClone();
        s2.getProfessor().setName("maer");
        s2.getProfessor().setAge(40);
        System.out.println("復(fù)制后的:s1 = " + s1);
        System.out.println("復(fù)制后的:s2 = " + s2);
    }
}

五、模式的結(jié)構(gòu)

介紹完淺克隆和深克隆,再回到原型模式上。

原型模式的UML類圖如下:

image

在原型模式結(jié)構(gòu)圖中包含如下幾個角色:

  • Prototype(抽象原型類):它是聲明克隆方法的接口,是所有具體原型類的公共父類,可以是抽象類也可以是接口,甚至還可以是具體實(shí)現(xiàn)類。
  • ConcretePrototype(具體原型類):它實(shí)現(xiàn)在抽象原型類中聲明的克隆方法,在克隆方法中返回自己的一個克隆對象。
  • Client(客戶類):讓一個原型對象克隆自身從而創(chuàng)建一個新的對象,在客戶類中只需要直接實(shí)例化或通過工廠方法等方式創(chuàng)建一個原型對象,再通過調(diào)用該對象的克隆方法即可得到多個相同的對象。由于客戶類針對抽象原型類Prototype編程,因此用戶可以根據(jù)需要選擇具體原型類,系統(tǒng)具有較好的可擴(kuò)展性,增加或更換具體原型類都很方便。

六、典型代碼

public interface Prototype {
    Prototype clone();
    void setAttr(String attr);
}

public class ConcretePrototype implements Prototype {
    private String attr; //成員屬性

    public void setAttr(String attr) {
        this.attr = attr;
    }

    public String getAttr() {
        return this.attr;
    }

    public Prototype clone() {
        Prototype prototype = new ConcretePrototype(); //創(chuàng)建新對象
        prototype.setAttr(this.attr);
        return prototype;
        /*Object object = null;
        try {
            object = super.clone();
        } catch (CloneNotSupportedException exception) {
            System.err.println("Not support cloneable");
        }
        return (Prototype) object;*/
    }
}

clone()方法是不能直接返回this的,相信都明白。

七、代碼示例

看過仙俠小說的都知道,修煉到高境界(肯定不是練氣,金丹的渣渣了),就可以煉制身外化身,這些化身都有自己的思想,可以修煉自己的功法,但卻聽從主體的命令,當(dāng)然有時候化身也會叛變,這里就以仙人化身為例:

public class Immortal implements Serializable {
    private String name;
    private int age;
    private String magicalPower;//神通
    private Wife wife;//道侶

    public Immortal(String name, int age, String magicalPower) {
        try {
            Thread.sleep(4000);//模擬仙人修煉
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.name = name;
        this.age = age;
        this.magicalPower = magicalPower;
    }

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

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

    public void setMagicalPower(String magicalPower) {
        this.magicalPower = magicalPower;
    }

    public void setWife(Wife wife) {
        this.wife = wife;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public String getMagicalPower() {
        return magicalPower;
    }

    public Wife getWife() {
        return wife;
    }

    //使用序列化實(shí)現(xiàn)深克隆
    public Immortal deepClone() throws IOException, ClassNotFoundException {
        //將對象寫入流中
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);

        //將對象從流中取出
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (Immortal) ois.readObject();
    }

    public String toString() {
        return "仙人 [姓名=" + name + ", 年齡=" + age + ", 神通="
                + magicalPower + ",道侶=" + wife.getName() + "]";
    }
}

public class Wife implements Serializable{
    private String name;
    private int age;

    public Wife(String name, int age){
        this.name = name;
        this.age = age;
    }

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

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public String toString(){
        return "道侶 [姓名=" + name + ", 年齡=" + age + "]";
    }
}

public class Client {
    public static void main(String[] args) {
        Wife yushiqie = new Wife("雨師妾", 3000);
        Immortal immortal = new Immortal("拓拔野", 2985, "天元訣,剎那芳華");
        immortal.setWife(yushiqie);
        System.out.println(immortal);

        try {
            //故事最后拓跋陪我最愛的雨師妾?dú)w隱,但姑射仙子卻沒了歸宿,這里假設(shè)拓跋分出一個分身
            Immortal incarnation = immortal.deepClone();
            Wife guyexianzi = incarnation.getWife();
            System.out.println(immortal.getWife() == incarnation.getWife()); //false
            guyexianzi.setName("姑射仙子");
            guyexianzi.setAge(2985);
            System.out.println(incarnation);
            incarnation.setWife(guyexianzi);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

八、優(yōu)點(diǎn)和缺點(diǎn)

8.1、優(yōu)點(diǎn)

原型模式的主要優(yōu)點(diǎn)如下:

  • 當(dāng)創(chuàng)建新的對象實(shí)例較為復(fù)雜時,使用原型模式可以簡化對象的創(chuàng)建過程,通過復(fù)制一個已有實(shí)例可以提高新實(shí)例的創(chuàng)建效率。
  • 擴(kuò)展性較好,由于在原型模式中提供了抽象原型類,在客戶端可以針對抽象原型類進(jìn)行編程,而將具體原型類寫在配置文件中,增加或減少產(chǎn)品類對原有系統(tǒng)都沒有任何影響。
  • 原型模式提供了簡化的創(chuàng)建結(jié)構(gòu),工廠方法模式常常需要有一個與產(chǎn)品類等級結(jié)構(gòu)相同的工廠等級結(jié)構(gòu),而原型模式就不需要這樣,原型模式中產(chǎn)品的復(fù)制是通過封裝在原型類中的克隆方法實(shí)現(xiàn)的,無須專門的工廠類來創(chuàng)建產(chǎn)品。
  • 可以使用深克隆的方式保存對象的狀態(tài),使用原型模式將對象復(fù)制一份并將其狀態(tài)保存起來,以便在需要的時候使用(如恢復(fù)到某一歷史狀態(tài)),可輔助實(shí)現(xiàn)撤銷操作。

8.2、缺點(diǎn)

原型模式的主要缺點(diǎn)如下:

  • 需要為每一個類配備一個克隆方法,而且該克隆方法位于一個類的內(nèi)部,當(dāng)對已有的類進(jìn)行改造時,需要修改源代碼,違背了“開閉原則”。
  • 在實(shí)現(xiàn)深克隆時需要編寫較為復(fù)雜的代碼,而且當(dāng)對象之間存在多重的嵌套引用時,為了實(shí)現(xiàn)深克隆,每一層對象對應(yīng)的類都必須支持深克隆,實(shí)現(xiàn)起來可能會比較麻煩。

九、適用環(huán)境

在以下情況下可以考慮使用原型模式:

  • 創(chuàng)建新對象成本較大(如初始化需要占用較長的時間,占用太多的CPU資源或網(wǎng)絡(luò)資源),新的對象可以通過原型模式對已有對象進(jìn)行復(fù)制來獲得,如果是相似對象,則可以對其成員變量稍作修改。
  • 一個對象需要提供給其他對象訪問,而且各個調(diào)用者可能都需要修改其值時,可以考慮使用原型模式拷貝多個對象供調(diào)用者使用,即保護(hù)性拷貝。
  • 需要避免使用分層次的工廠類來創(chuàng)建分層次的對象,并且類的實(shí)例對象只有一個或很少的幾個組合狀態(tài),通過復(fù)制原型對象得到新實(shí)例可能比使用構(gòu)造函數(shù)創(chuàng)建一個新實(shí)例更加方便。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容