設計模式之原型模式

定義

指原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。

不需要知道任何創建細節,不調用構造函數。

類型

創建型

適用場景

①. 類初始化消耗較多資源。
②. new產生的一個對象需要非常繁瑣的過程(數據準備、訪問權限等)。
③. 構造函數比較複雜。
④. 循環體中生產大量對象時。

優缺點

優點:
①. 原型模式性能比直接new一個對象性能高。
②. 簡化創建過程。

缺點:
①. 必須具備克隆方法。
②. 對克隆複雜對象或對克隆出的對象進行復雜改造時,容易引入風險。
③. 深拷貝、淺拷貝要運用得當。

代碼實現

以發送郵件並保留原始郵件爲例。
首先,創建一個郵件對象。

public class Mail implements Cloneable {

    private String name;
    private String emailAddress;
    private String content;

    public Mail() {
        System.out.println("init Constructor");
    }

    public String getName() {
        return name;
    }

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

    public String getEmailAddress() {
        return emailAddress;
    }

    public void setEmailAddress(String emailAddress) {
        this.emailAddress = emailAddress;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "Mail{" +
                "name='" + name + '\'' +
                ", emailAddress='" + emailAddress + '\'' +
                ", content='" + content + '\'' +
                '}' + "," + super.toString();
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        System.out.println("克隆對象");
        return super.clone();
    }
}

Mail這個對象要想使用克隆的方法,需要實現Cloneable接口,並重寫clone()方法。爲了直觀的看到輸出結果,修改了toString()方法。
需要發送郵件和存儲原始郵件,所以,寫一個操作郵件的工具類。

public class MailUtil {

    /**
     * 發送郵件
     */
    public static void sendMail(Mail mail) {
        String mailContent = "向姓名爲:{0},郵件地址爲:{1},郵件內容爲:{2}的同學發送郵件";
        System.out.println(MessageFormat.format(mailContent, mail.getName(), mail.getEmailAddress(), mail.getContent()));
    }

    /**
     * 存儲原始郵件
     */
    public static void saveOriginMail(Mail mail) {
        System.out.println("存儲原始origin記錄,originMail:" + mail.getContent());
    }

}

最後編寫測試類。

public class MainTest{
        public static void main(String[] args) throws CloneNotSupportedException {
        Mail mail = new Mail();
        mail.setContent("初始化模板");
        System.out.println("初始化mail:" + mail + "\n");

        for (int i = 0; i < 10; i++) {
            //用原始的對象去克隆,使用克隆出來的對象去賦值
            Mail tempMail = (Mail) mail.clone();
            tempMail.setName("小明" + i);
            tempMail.setEmailAddress(i + "@gmail.com");
            tempMail.setContent("恭喜您,獲得了Java設計模式這門課程 ");
            MailUtil.sendMail(tempMail);
            System.out.println("克隆的mail:" + tempMail + "\n");
        }
        //存儲的時候,還是存儲原始的mail對象
        MailUtil.saveOriginMail(mail);
    }
}

通過控制檯查看結果

C:\android\java\jdk1.8\bin\java.exe...

init Constructor
初始化mail:Mail{name='null', emailAddress='null', content='初始化模板'},com.qfcwx.design.pattern.creational.prototype.Mail@29453f44

克隆對象
向姓名爲:小明0,郵件地址爲:[email protected],郵件內容爲:恭喜您,獲得了Java設計模式這門課程 的同學發送郵件
克隆的mail:Mail{name='小明0', emailAddress='[email protected]', content='恭喜您,獲得了Java設計模式這門課程 '},com.qfcwx.design.pattern.creational.prototype.Mail@5cad8086

克隆對象
向姓名爲:小明1,郵件地址爲:[email protected],郵件內容爲:恭喜您,獲得了Java設計模式這門課程 的同學發送郵件
克隆的mail:Mail{name='小明1', emailAddress='[email protected]', content='恭喜您,獲得了Java設計模式這門課程 '},com.qfcwx.design.pattern.creational.prototype.Mail@6e0be858

克隆對象
向姓名爲:小明2,郵件地址爲:[email protected],郵件內容爲:恭喜您,獲得了Java設計模式這門課程 的同學發送郵件
克隆的mail:Mail{name='小明2', emailAddress='[email protected]', content='恭喜您,獲得了Java設計模式這門課程 '},com.qfcwx.design.pattern.creational.prototype.Mail@61bbe9ba

克隆對象
向姓名爲:小明3,郵件地址爲:[email protected],郵件內容爲:恭喜您,獲得了Java設計模式這門課程 的同學發送郵件
克隆的mail:Mail{name='小明3', emailAddress='[email protected]', content='恭喜您,獲得了Java設計模式這門課程 '},com.qfcwx.design.pattern.creational.prototype.Mail@610455d6

克隆對象
向姓名爲:小明4,郵件地址爲:[email protected],郵件內容爲:恭喜您,獲得了Java設計模式這門課程 的同學發送郵件
克隆的mail:Mail{name='小明4', emailAddress='[email protected]', content='恭喜您,獲得了Java設計模式這門課程 '},com.qfcwx.design.pattern.creational.prototype.Mail@511d50c0

克隆對象
向姓名爲:小明5,郵件地址爲:[email protected],郵件內容爲:恭喜您,獲得了Java設計模式這門課程 的同學發送郵件
克隆的mail:Mail{name='小明5', emailAddress='[email protected]', content='恭喜您,獲得了Java設計模式這門課程 '},com.qfcwx.design.pattern.creational.prototype.Mail@60e53b93

克隆對象
向姓名爲:小明6,郵件地址爲:[email protected],郵件內容爲:恭喜您,獲得了Java設計模式這門課程 的同學發送郵件
克隆的mail:Mail{name='小明6', emailAddress='[email protected]', content='恭喜您,獲得了Java設計模式這門課程 '},com.qfcwx.design.pattern.creational.prototype.Mail@5e2de80c

克隆對象
向姓名爲:小明7,郵件地址爲:[email protected],郵件內容爲:恭喜您,獲得了Java設計模式這門課程 的同學發送郵件
克隆的mail:Mail{name='小明7', emailAddress='[email protected]', content='恭喜您,獲得了Java設計模式這門課程 '},com.qfcwx.design.pattern.creational.prototype.Mail@1d44bcfa

克隆對象
向姓名爲:小明8,郵件地址爲:[email protected],郵件內容爲:恭喜您,獲得了Java設計模式這門課程 的同學發送郵件
克隆的mail:Mail{name='小明8', emailAddress='[email protected]', content='恭喜您,獲得了Java設計模式這門課程 '},com.qfcwx.design.pattern.creational.prototype.Mail@266474c2

克隆對象
向姓名爲:小明9,郵件地址爲:[email protected],郵件內容爲:恭喜您,獲得了Java設計模式這門課程 的同學發送郵件
克隆的mail:Mail{name='小明9', emailAddress='[email protected]', content='恭喜您,獲得了Java設計模式這門課程 '},com.qfcwx.design.pattern.creational.prototype.Mail@6f94fa3e

存儲原始origin記錄,originMail:初始化模板

因爲截圖還要上傳至服務器,感覺很麻煩,所以直接將結果複製出來了。

通過上面的結果可知,雖然原始郵件通過new創建了對象。但是克隆對象都是通過原始對象的clone()方法生成的,並沒有使用構造器,每個對象的內存地址都不同。還保留了最原始的郵件。

擴展(深克隆、淺克隆)

創建一個實體類

public class Pig implements Cloneable {

    private String name;
    private Date birthday;

    public Pig() {
    }

    public String getName() {
        return name;
    }

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

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "Pig{" +
                "name='" + name + '\'' +
                ", birthday=" + birthday +
                '}' + "," + super.toString();
    }

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

這個實體類,有姓名和生日兩個屬性。也實現了Cloneable接口,重寫clone()方法並重寫toString()方法。
編寫測試類。

public class Test {

    public static void main(String[] args) throws CloneNotSupportedException {
        Pig pig = new Pig();
        Date birthday = new Date(0L);
        pig.setName("佩奇");
        pig.setBirthday(birthday);

        Pig newPig = (Pig) pig.clone();
        System.out.println(pig);
        System.out.println(newPig);

        //修改時間
        pig.getBirthday().setTime(666666666666L);
        System.out.println(pig);
        System.out.println(newPig);
    }
}

這裏創建了一個Pig對象。然後使用clone()方法進行克隆。然後輸出兩個對象的結果。
底下對原始對象的生日屬性進行了修改。再一次輸出了兩個對象的結果。

預期結果:
原始對象(pig),兩次輸出結果不一樣。
克隆對象(newPig),兩次輸出結果一樣。

Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970},com.qfcwx.design.pattern.creational.prototype.colne.Pig@610455d6
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970},com.qfcwx.design.pattern.creational.prototype.colne.Pig@511d50c0
Pig{name='佩奇', birthday=Sat Feb 16 09:11:06 CST 1991},com.qfcwx.design.pattern.creational.prototype.colne.Pig@610455d6
Pig{name='佩奇', birthday=Sat Feb 16 09:11:06 CST 1991},com.qfcwx.design.pattern.creational.prototype.colne.Pig@511d50c0

查看結果的時候,一下就驚呆了,這是什麼操作啊?明明只修改了原始對象(pig)的屬性值,爲什麼克隆對象的birthday值也會改變?

接着,博主進行debug發現,雖然兩個Pig對象不一樣,但是通過克隆出來的newPig對象和原始對象的birthday屬性是同一個Date對象。也就是說,當我們修改日期對象的時候,另一個對象中的這個日期對象也會發生變化。所以,這種克隆的方式就是淺克隆。

那麼如何進行深克隆,使得改變原始對象的值,不影響克隆對象的值?
其實也很簡單,具體操作就是,再重寫clone()方法的時候,對生日這個對象進行克隆。修改上面Pig實體類中的clone()方法。

    @Override
    protected Object clone() throws CloneNotSupportedException {

        Pig pig = (Pig) super.clone();

        //深克隆(單獨對生日對象進行克隆)
        pig.birthday = (Date) pig.birthday.clone();
        return pig;

        //默認的克隆方式(淺克隆)
//        return super.clone();
    }

修改代碼如上,將原先的克隆邏輯注掉,改爲對引用對象(Date)也進行克隆。然後重新運行程序,查看結果。就達到了當初預期的效果。
所以,如果對象屬性中有引用數據類型,那麼在進行克隆的時候,也是要對屬性進行克隆的,這樣才能避免不必要的BUG。

相關源碼

  1. ArrayList,HashMap中都重寫了clone方法
  2. Mybatis中的CacheKey

結語

1024程序員節是廣大程序員的共同節日。1024是2的十次方,二進制計數的基本計量單位之一。針對程序員經常週末加班與工作日熬夜的情況,部分互聯網機構倡議每年的10月24日爲1024程序員節,在這一天建議程序員拒絕加班。
程序員就像是一個個1024,以最低調、踏實、核心的功能模塊搭建起這個科技世界。1G=1024M,而1G與1級諧音,也有一級棒的意思。

今天是程序員節,也祝各位讀者程序員節快樂。希望以後,我們只漲薪水,不改需求。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章