設計模式之原型模式

設計模式系列專題(更新中)

設計模式之策略模式java

設計模式之代理模式設計模式

設計模式之專題模式bash

設計模式之建造者模式markdown

設計模式之裝飾模式ide

設計模式之觀察者模式函數

設計模式之責任鏈模式post

設計模式之命令模式測試

設計模式之門面模式this

1、定義

       用原型實例指定建立對象的種類,而且經過拷貝這些原型建立新的對象。(來源:百度百科)spa

        原型模式能夠說是最簡單的設計模式之一了,從定義就能夠知道它的所有內容。經過從「原型實例」中拷貝出新的對象。拷貝出新的對象,因此也能夠認爲原型模式就是建立對象的另外一種方式。 Java中Object已經爲咱們提供了clone方法,此方法是一個native方法(C、或C++實現的),用來幫咱們拷貝對象,不過這種拷貝是淺拷貝。

2、拷貝

淺拷貝

        被複制對象(一個新的對象實例)的全部變量都含有與原來的對象相同的值,對於基本數據類型的屬性複製一份給新產生的對象,對於非基本數據類型的屬性僅僅複製一份引用給新產生的對象。即對於對象類型的實例變量,新拷貝出來的對象裏面的變量指向的對象與原來對象是同一個對象。

深拷貝

        被複制對象(一個新的對象實例)的全部變量都含有與原來的對象相同的值,而那些引用其餘對象的變量將指向被複制過的新對象(新內存空間),而再也不是原有的那些被引用的對象。

        從這兩個定義不知你是否知道了淺拷貝,深拷貝的區別,文字說明可能不太好理解,接下來看個小例子,可能就能一目瞭然。

package com.prototype;
/**
*父親類
*/
public class Father {
    private String name;
    private int age;
    //省略get、set方法
}

/**
*學生類,java中實現拷貝的類必須實現Cloneable接口,其實這只是一個標記接口,
*接口並無實際內容,不然調Object的clone方法會拋出異常
*/
public class Student implements Cloneable{

    private int stuNum;
    private String name;

    private Father father;

    public Student Clone(){
        Student stu = null;
        try {
            //調用Object提供的clone方法來拷貝
            stu = (Student) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return  stu;
    }

    public  void showStuMsg(){
        System.out.println("學生名稱:"+name+",學號:"+stuNum+",父親名稱:"+ father.getName()+",父親年齡:"+ father.getAge());
    }
    //省略get、set方法
}

/**
測試代碼
*/
package com.prototype;

public class TestMain {

    public static void main(String[] args) {

        //建立學生zhangsan
        Student zhangsan = new Student();
        zhangsan.setName("張三");
        zhangsan.setStuNum(14);
        Father fatherzhangsan = new Father();
        fatherzhangsan.setName("張三的老爸");        
        fatherzhangsan.setAge(47);        
        zhangsan.setFather(fatherzhangsan);
        //從zhangsan中拷貝出李四
        Student lisi = zhangsan.Clone();
        //設置李四名字,學號
        lisi.setName("李四");
        lisi.setStuNum(15);
        //改變李四的父親名稱,年齡
        lisi.getFather().setName("李四的老爸");
        lisi.getFather().setAge(48);
        //展現學生信息
        zhangsan.showStuMsg();
        lisi.showStuMsg();
    }
}
複製代碼

執行結果


能夠看到,咱們改了李四的父親的名稱,年齡,結果張三的父親也跟着改變了,這實際上是由於張3、李四指向同一個父親對象了。而改變學號,並無同步改變了張三的學號。這就是淺拷貝,對基本類型變量會建立新變量;對對象類型變量,變量同被拷貝者指向同樣的對象。

深拷貝

那麼怎麼實現深拷貝呢?有兩種方式:

方式一

//讓父親類提供克隆方法
package com.prototype;

public class Father implements Cloneable{
    private String name;
    private int age;
    //省略get、set方法
    public Father clone(){
        Father father = null;
        try {
            father = (Father) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return father;
    }
}
//學生類clone時,克隆父親類
package com.prototype;

public class Student implements Cloneable{
    private int stuNum;
    private String name;
    private Father father;

    public Student Clone(){
        Student stu = null;
        try {
            stu = (Student) super.clone();
            //克隆父類,並設置到拷貝出來的學生類裏
            stu.setFather(father.clone());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return  stu;
    }

    //省略get、set方法
    public  void showStuMsg(){
        System.out.println("學生名稱:"+name+",學號:"+stuNum+",父親名稱:"+ father.getName()+",父親年齡:"+ father.getAge());
    }
}
//測試代碼同上...複製代碼

執行結果,能夠看到張3、李四各有本身父親了。


方式二

不須要實現Cloneable接口了,父親類和學生類都實現Serializable,經過對象流來拷貝

//父類
package com.prototype;

import java.io.Serializable;

public class Father implements Serializable{
    private String name;
    private int age;
    //省略get、set方法
}

//學生類
package com.prototype;

import java.io.*;

public class Student implements Serializable{

    private int stuNum;
    private String name;
    private Father father;
    //經過Object流來拷貝
    public Student deepClone(){
        ByteArrayOutputStream bo = new ByteArrayOutputStream();
        ObjectOutputStream oo = null;
        try {
            oo = new ObjectOutputStream(bo);
            //將當前對象寫入到流裏面
            oo.writeObject(this);
            ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
            ObjectInputStream oi = new ObjectInputStream(bi);
            //從流裏面對出對象,即位拷貝出來的對象
            return (Student) oi.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        }catch (ClassNotFoundException e){
            e.printStackTrace();
        }
        return null;
    }
    //省略get、set方法
    public  void showStuMsg(){
        System.out.println("學生名稱:"+name+",學號:"+stuNum+",父親名稱:"+ father.getName()+",父親年齡:"+ father.getAge());
    }
}
//測試代碼同上,只修改調用的拷貝方法名

Student lisi = zhangsan.deepClone();複製代碼

執行結果同方式一。

3、UML圖

  照例,咱們依然經過例子來了解這個「原型模式」這個設計模式。場景:咱們有一份試卷的原型,但願經過這份試卷拷貝出多份,供考試使用。看看原型模式的UML圖

Prototype抽象類(原型抽象類)定義了一個clone方法。

TestPaper具體的原型類,實現clone方法。


4、代碼實現

看過了上面的淺、深拷貝,代碼實現也很簡單了,只需按照UML圖編寫對應的幾個類便可。這裏咱們增長一個閱讀題類,經過深拷貝來複制試卷。

//原型抽象類
package com.prototype;

public abstract class Prototype {
    public abstract Prototype Clone();
}

//閱讀題類************************************************************************
package com.prototype;
/**
 * 閱讀題
 */
public class ReadingTopics extends Prototype implements Cloneable{
    private String topicTitle;
    private String topicContent;

    public ReadingTopics(String topicTitle, String topicContent) {
        this.topicTitle = topicTitle;
        this.topicContent = topicContent;
    }
    //省略get、set方法
    @Override
    public Prototype Clone() {
        ReadingTopics readingTopics = null;
        try {
            readingTopics = (ReadingTopics) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return readingTopics;
    }

}

//試卷類*************************************************************************
package com.prototype;

public class TestPager extends Prototype implements Cloneable{

    private String paperTitle;//試卷標題
    private ReadingTopics readingTopics;//閱讀理解

    public  void showPagerContent(){
        System.out.println("*******試卷內容*******");
        System.out.println("試卷:"+paperTitle);
        System.out.println("閱讀題:"+readingTopics.getTopicTitle()+","+readingTopics.getTopicContent());
        System.out.println("********結束*********");
    }
    //省略get、set方法
    @Override
    public Prototype Clone() {
        TestPager testPager = null;
        try {
            testPager = (TestPager) super.clone();
            testPager.setReadingTopics((ReadingTopics) readingTopics.Clone());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return testPager;
    }
}
//測試類**************************************************************************
package com.prototype;

public class TestMain {

    public static void main(String[] args) {
        TestPager testPager = new TestPager();
        testPager.setPaperTitle("高二語文試卷");
        testPager.setReadingTopics(new ReadingTopics("水滸傳選讀","武松打虎片斷!!"));

        TestPager cloneObj = (TestPager) testPager.Clone();
        cloneObj.getReadingTopics().setTopicTitle("三過演義");
        cloneObj.getReadingTopics().setTopicContent("草船借箭片斷!!");
        testPager.showPagerContent();
        cloneObj.showPagerContent();

    }
}
複製代碼

執行結果,能夠看到咱們成功拷貝出一份新的試卷。


5、結論總結

很明顯,原型模式就是從已有對象拷貝出一個狀態與已有對象一摸同樣的新對象。主要的應用場景:構造函數執行時間很長,執行效率低,便可採用拷貝(克隆,並不走構造方法)。

參考:《大話設計模式》

相關文章
相關標籤/搜索