Java設計模式之原型模式

Java設計模式之原型模式

//首先新建一個類Mail
public class Mail {
    //這個類總共有三個屬性
    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 + '\'' +
                '}';
    }
}

//新建另外一個類
public class MailUtil {
    public static void sendMail(Mail mail){
        String content="{0}同學,郵件地址:{1},內容:{2}";
        System.out.println(MessageFormat.format(content,mail.getName()
                ,mail.getEmailAddress(),mail.getContent()));
    }
    public static void saveOriginalContent(Mail mail){
        System.out.println("保存原始內容:"+mail.getContent());
    }
}


//調用
public static void main(String[]a){
        Mail mail=new Mail();
        mail.setContent("初始化模板");
        for (int i=0;i<10;i++){
            mail.setName("姓名"+i);
            mail.setEmailAddress("郵箱"+i);
            mail.setContent("郵件內容。。。");
            MailUtil.sendMail(mail);
        }
        MailUtil.saveOriginalContent(mail);

    }


//結果
Mail class constructor
姓名0同學,郵件地址:郵箱0,內容:郵件內容。。。
姓名1同學,郵件地址:郵箱1,內容:郵件內容。。。
姓名2同學,郵件地址:郵箱2,內容:郵件內容。。。
姓名3同學,郵件地址:郵箱3,內容:郵件內容。。。
姓名4同學,郵件地址:郵箱4,內容:郵件內容。。。
姓名5同學,郵件地址:郵箱5,內容:郵件內容。。。
姓名6同學,郵件地址:郵箱6,內容:郵件內容。。。
姓名7同學,郵件地址:郵箱7,內容:郵件內容。。。
姓名8同學,郵件地址:郵箱8,內容:郵件內容。。。
姓名9同學,郵件地址:郵箱9,內容:郵件內容。。。
保存原始內容:郵件內容。。。

這裏有兩個問題,第一是MailUtil.saveOriginalContent(mail)方法應該是保存原始內容“初始化模板”而不是for循環裏面最後被賦值了。第二個問題是考慮實例化Mail對象比較消耗資源的問題。

下面看看引入原型模式後的優雅實現方法。

//Mail類實現Cloneable接口
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 方法");
        return super.clone();
    }
}



public class MailUtil {
    public static void sendMail(Mail mail){

    }
    public static void saveOriginalContent(Mail mail){
        System.out.println("保存原始內容:"+mail.getContent());
    }
}


//調用
public static void main(String[]a) throws CloneNotSupportedException {
        Mail mail=new Mail();
        mail.setContent("初始化模板");
        System.out.println("初始化的mail對象:"+mail);
        for (int i=0;i<10;i++){
            Mail tmpMail= (Mail) mail.clone();
            tmpMail.setName("姓名"+i);
            tmpMail.setEmailAddress("郵箱"+i);
            tmpMail.setContent("郵件內容。。。");
            MailUtil.sendMail(tmpMail);
            System.out.println("克隆的mail對象:"+tmpMail);
        }
        MailUtil.saveOriginalContent(mail);
    }


//結果:@符號後面的地址不同表示實例化的mail對象不同.
Mail class constructor
初始化的mail對象:Mail{name='null', emailAddress='null', content='初始化模板'}com.zk.javatest.prototype.Mail@1540e19d
clone 方法
克隆的mail對象:Mail{name='姓名0', emailAddress='郵箱0', content='郵件內容。。。'}com.zk.javatest.prototype.Mail@677327b6
clone 方法
克隆的mail對象:Mail{name='姓名1', emailAddress='郵箱1', content='郵件內容。。。'}com.zk.javatest.prototype.Mail@14ae5a5
clone 方法
克隆的mail對象:Mail{name='姓名2', emailAddress='郵箱2', content='郵件內容。。。'}com.zk.javatest.prototype.Mail@7f31245a
clone 方法
克隆的mail對象:Mail{name='姓名3', emailAddress='郵箱3', content='郵件內容。。。'}com.zk.javatest.prototype.Mail@6d6f6e28
clone 方法
克隆的mail對象:Mail{name='姓名4', emailAddress='郵箱4', content='郵件內容。。。'}com.zk.javatest.prototype.Mail@135fbaa4
clone 方法
克隆的mail對象:Mail{name='姓名5', emailAddress='郵箱5', content='郵件內容。。。'}com.zk.javatest.prototype.Mail@45ee12a7
clone 方法
克隆的mail對象:Mail{name='姓名6', emailAddress='郵箱6', content='郵件內容。。。'}com.zk.javatest.prototype.Mail@330bedb4
clone 方法
克隆的mail對象:Mail{name='姓名7', emailAddress='郵箱7', content='郵件內容。。。'}com.zk.javatest.prototype.Mail@2503dbd3
clone 方法
克隆的mail對象:Mail{name='姓名8', emailAddress='郵箱8', content='郵件內容。。。'}com.zk.javatest.prototype.Mail@4b67cf4d
clone 方法
克隆的mail對象:Mail{name='姓名9', emailAddress='郵箱9', content='郵件內容。。。'}com.zk.javatest.prototype.Mail@7ea987ac
保存原始內容:初始化模板

 

原型模式中的淺克隆與深克隆

先來看看淺克隆


//新建類Pig
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
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

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


//調用
public static void main(String[] a) throws CloneNotSupportedException {
        Date birthday=new Date(0L);
        Pig pig1=new Pig("佩奇",birthday);
        Pig pig2= (Pig) pig1.clone();
        System.out.println(pig1);
        System.out.println(pig2);

        pig1.getBirthday().setTime(666666666666L);
        System.out.println("修改後:"+pig1);
        System.out.println("修改後:"+pig2);
    }


//結果:
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com.zk.clone.Pig@4c75cab9
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com.zk.clone.Pig@1ef7fe8e
修改後:Pig{name='佩奇', birthday=Sat Feb 16 09:11:06 CST 1991}com.zk.clone.Pig@4c75cab9
修改後:Pig{name='佩奇', birthday=Sat Feb 16 09:11:06 CST 1991}com.zk.clone.Pig@1ef7fe8e

從運行結果可以看出,修改了pig1對象的birthday屬性,pig2對象的birthday屬性也被修改了。看看下面這張調試過程的圖。

雖然pig1與pig1克隆出的對象pig2是兩個不同的對象,但是對象裏面的birthday屬性是引用的是同一個對象。因此也就看到了上面的兩個birthday都被修改了。

這就是淺克隆。

 

下面看看深克隆:修改clone方法。

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
    protected Object clone() throws CloneNotSupportedException {
        //深克隆
        Pig pig= (Pig) super.clone();
        //將引用對象Date也克隆
        pig.birthday=(Date) pig.birthday.clone();
        return pig;
    }

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


//運行結果:
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com.zk..clone.Pig@6d6f6e28
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com.zk..clone.Pig@135fbaa4
修改後:Pig{name='佩奇', birthday=Sat Feb 16 09:11:06 CST 1991}com.zk.clone.Pig@6d6f6e28
修改後:Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com.zk.clone.Pig@135fbaa4

此時修改pig1對象的birthday屬性不會影響pig2的birthday屬性。

因此,使用原型模式時,一定要注意淺克隆與深克隆,否則很容易導致bug。

 

原型模式破壞單例模式

//餓漢式單例模式,實現Cloneable接口
public class HungrySingleton implements Serializable,Cloneable{
    private HungrySingleton(){
        if (hungrySingleton!=null){
            throw new RuntimeException("單例模式的構造器禁止反射");
        }
    }

    private final static HungrySingleton hungrySingleton=new HungrySingleton();

    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }

    private Object readResolve(){
        return  hungrySingleton;
    }

    //重寫此方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

//調用
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);

//結果
com.zk.javatest.prototype.HungrySingleton@677327b6
com.zk.javatest.prototype.HungrySingleton@14ae5a5


從運行結果可以看出,兩個實例對象不相同,單例模式被破壞。那如果防止這種破壞呢?很簡單,重寫clone()方法即可。

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

 

 

 

 

 

 

 

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