1. 程式人生 > >java設計模式——原型模式

java設計模式——原型模式

一個 image 另一個 [] throws arr pig ble mage

一. 定義與類型

定義:指原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。不需要知道任何創建的細節,不調用構造函數

類型:創建型

二.使用場景

類初始化消耗較多資源

new 產生的一個對象需要非常繁瑣的過程(數據準備,訪問權限等)

構造函數比較復雜

循環體中生產大量對象時

三.優缺點

優點:

  原型模式性能比直接new一個對象性能高,簡化創建過程

缺點:

  必須配備克隆方法,

  對克隆復雜對象或對克隆出的對象進行復雜改造時,容易引入風險

  深拷貝,淺拷貝要運用得當

四. 擴展

深克隆:對於引用類型,如果需要指向不同的對象,而對於某個對象的引用類型的時候,必須要顯式的去寫對那個屬性進行深克隆

淺克隆:

五. Coding

/**
 * @program: designModel
 * @description:
 * @author: YuKai Fan
 * @create: 2018-12-13 16:11
 **/
public class Mail implements Cloneable {
    private String name;
    private String emailAddress;
    private String content;
    public Mail() {
        System.out.println("Mail Class 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("clone mail object"); return super.clone(); } }
/**
 * @program: designModel
 * @description:
 * @author: YuKai Fan
 * @create: 2018-12-13 16:17
 **/
public class MailUtil {
    public static void sendMail(Mail mail) {
        String outputContent = "向{0}同學,郵件地址:{1},郵件內容:{2}發送成功";
        System.out.println(MessageFormat.format(outputContent,mail.getName(),mail.getEmailAddress(),mail.getContent()));
    }

    public static void saveOriginMailRecord(Mail mail) {
        System.out.println("存儲originMail記錄,originMail:" + mail.getContent());
    }
}
/**
 * @program: designModel
 * @description:
 * @author: YuKai Fan
 * @create: 2018-12-13 16:20
 **/
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Mail mail = new Mail();
        mail.setContent("初始化模板");
        System.out.println("初始化mail:" + mail);
        for (int i = 0; i < 10; i++) {
            //克隆的時候,並不會使用原對象的構造器
            Mail mailTemp = (Mail) mail.clone();
            mailTemp.setName("姓名" + i);
            mailTemp.setEmailAddress("姓名" + i + "@qq.com");
            mailTemp.setContent("恭喜您,中獎了");
            MailUtil.sendMail(mailTemp);
        }
        MailUtil.saveOriginMailRecord(mail);
    }
}

從上面的代碼不難看出,其實原型模式就是實現了cloneable接口,在創建一個不同的對象,來完成郵件的發送,而保留了原來的郵件模板。由於clone不會,調用原對象的構造器,所以在效率上比直接new 對象要高。但是,因為在Mail類中的屬性都是簡單類型,所以在clone的時候,基本上不會出現上面問題。但是看下面一個實體:

/**
 * @program: designModel
 * @description:
 * @author: YuKai Fan
 * @create: 2018-12-13 16:33
 **/
public class Pig implements Cloneable {
    private String name;
    private Date birthDay;

    public Pig(String name, Date birthDay) {
        this.name = name;
        this.birthDay = birthDay;
    }

    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 +
                ‘}‘;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
/**
 * @program: designModel
 * @description:
 * @author: YuKai Fan
 * @create: 2018-12-13 16:34
 **/
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Date birthDay = new Date(0L);
        Pig pig = new Pig("佩奇", birthDay);
        Pig pig1 = (Pig) pig.clone();
        System.out.println(pig);
        System.out.println(pig1);

        pig.getBirthDay().setTime(6666666666666L);
        /**
         * 通過上面對birthDay進行操作,如果是淺拷貝,那麽pig與pig1的date對象都會改變:
         *  因為在淺拷貝的時候,兩個對象中的引用對象date,都是引用同一個對象,所以改變了一個,那麽兩個都會改變
         *
         * 如果是深拷貝,那麽pig與pig1引用的date對象就是不一樣的,改變其中一個,對另一個並沒有影響。
         *
         * 由於深克隆,淺克隆的關系,也算是原型模式的一個坑。(原則是,都會使用深克隆,不然就算是給項目埋坑)
         *
         */
        System.out.println(pig);
        System.out.println(pig1);
    }
}

輸出結果為:

技術分享圖片

上面的註釋和結果,其實也清楚的看到了。我只改變了pig對象中的birthDay,但是pig1中也改變了。如果clone的對象中存在引用類型的對象,那麽如果是淺拷貝,拷貝與被拷貝出的對象的引用對象都是指向同一地址的,所以改變其中一個,另一個也會改變。這時候,如果根據需求就必須使用深拷貝。

也就是對於對象中的引用對象也要進行clone。看下面代碼:

/**
 * @program: designModel
 * @description:
 * @author: YuKai Fan
 * @create: 2018-12-13 16:33
 **/
public class Pig implements Cloneable {
    private String name;
    private Date birthDay;

    public Pig(String name, Date birthDay) {
        this.name = name;
        this.birthDay = birthDay;
    }

    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 +
                ‘}‘;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Pig pig = (Pig) super.clone();

        //深克隆
        pig.birthDay = (Date) pig.birthDay.clone();
        return pig;
    }
}

輸出結果:

技術分享圖片

可以看到,Pig中重寫了clone方法,對Date對象也進行了clone,從而使得,引用對象指向不同的地址。所以改變pig中的birthDay,對於pig1並沒有影響。

由於clone方法,可以用原型模式拷貝來破壞單例模式:

/**
 * @program: designModel
 * @description:
 * @author: YuKai Fan
 * @create: 2018-12-13 16:34
 **/
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        /**
         * 使用原型模式,克隆破壞單例模式
         *
         * 這種情況的解決方式:
         * 要麽單例模式類不去實現Cloneable接口,要麽就重寫clone方法,直接返回getInstance()方法,這個對象的實例
         */
        HungrySingleton hungrySingleton = HungrySingleton.getInstance();
        Method method = hungrySingleton.getClass().getDeclaredMethod("clone");
        method.setAccessible(true);
        HungrySingleton cloneHungrySingleton = (HungrySingleton) method.invoke(hungrySingleton);
        System.out.println(hungrySingleton);
        System.out.println(cloneHungrySingleton);
    }
}

六. 源碼分析

基本上只要知道了哪些類使用了Cloneable就知道,原型模式如何使用。

比如ArrayList,HashMap類都重寫了clone方法

Mybatis中的CacheKey類也重寫了clone()方法

java設計模式——原型模式