設計模式解密(18)- 原型模式


1、簡介

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

功能:①是通過克隆來創建新的對象實例;②是為克隆出來的新的對象實例復制原型實例屬性的值。

本質:通過克隆來創建新的對象實例。

英文:Prototype

類型:創建型

2、類圖及組成

(引)類圖:

組成:

  Prototype:聲明一個克隆自身的接口,用來約束想要克隆自己的類,要求它們都要實現這里定義的克隆方法。

  ConcretePrototype:實現Prototype接口的類,這些類真正實現了克隆自身的功能。

  Client:使用原型的客戶端,首先要獲取到原型實例對象,然后通過原型實例克隆自身來創建新的對象實例。

 

標准代碼結構:

/**
 * 先來看看原型接口的定義 : 聲明一個克隆自身的接口
 */
public interface Prototype {
    /**
     * 克隆自身的方法
     * @return 一個從自身克隆出來的對象
     */
    public Prototype clone();
}

/**
 * 具體的原型實現對象 : 克隆的具體實現對象
 * 為了跟上面原型模式的結構示意圖保持一致,因此這兩個具體的原型實現對象,都沒有定義屬性。事實上,在實際使用原型模式的應用中,原型對象多是有屬性的,克隆原型的時候也是需要克隆原型對象的屬性的,特此說明一下。
 */
public class ConcretePrototype implements Prototype {
    public Prototype clone() {
       //最簡單的克隆,新建一個自身對象,由於沒有屬性,就不去復制值了
       Prototype prototype = new ConcretePrototype();
       return prototype;
    }
}

/**
 * 使用原型的客戶端
 */
public class Client {
    /**
     * 持有需要使用的原型接口對象
     */
    private Prototype prototype;
    
    /**
     * 構造方法,傳入需要使用的原型接口對象
     * @param prototype 需要使用的原型接口對象
     */
    public Client(Prototype prototype){
        this.prototype = prototype;
    }
    
    /**
     * 示意方法,執行某個功能操作
     */
    public void operation(){
        //會需要創建原型接口的對象
        Prototype newPrototype = prototype.clone();
    }
}

上面的代碼組成是為了讓大家清楚了解原型模式的結構,Java中已經提供Cloneable接口,所以在實現原型模式不必在實現Prototype原型接口,直接實現Cloneable接口即可;

Java實現原型模式的兩個要點:

1、實現Cloneable接口。在java語言有一個Cloneable接口,它的作用只有一個,就是在運行時通知虛擬機可以安全地在實現了此接口的類上使用clone方法。在java虛擬機中,只有實現了這個接口的類才可以被拷貝,否則在運行時會拋出 CloneNotSupportedException異常。

2、重寫Object類中的clone方法。Java中,所有類的父類都是Object類,Object類中有一個clone方法,作用是返回對象的一個拷貝,但是其作用域protected類型的,一般的類無法調用,因此,Prototype類需要將clone方法的作用域修改為public類型。

Java原型模式代碼結構:

public class PrototypeClass implements Cloneable{    
    //覆寫父類Object方法  
    @Override  
    public PrototypeClass clone(){  
        PrototypeClass prototypeClass = null;  
        try {  
            prototypeClass = (PrototypeClass)super.clone();  
        } catch (CloneNotSupportedException e) {  
            //異常處理  
        }  
        return prototypeClass;  
    }  
} 

注意:

  Object類的clone方法只會拷貝java中的8中基本類型以及他們的封裝類型,另外還有String類型。對於數組、容器對象、引用對象等都不會拷貝,這就是淺拷貝。如果要實現深拷貝,必須將原型模式中的數組、容器對象、引用對象等另行拷貝。

3、淺拷貝與深拷貝

▶ 淺拷貝(shallow copy)

  被復制對象的所有變量都含有與原來的對象相同的值(僅對於簡單的值類型數據),而所有的對其他對象的引用都仍然指向原來的對象。換言之,只負責克隆按值傳遞的數據(比如:基本數據類型、String類型)。

▶ 深拷貝 (deep copy)

  被復制對象的所有的變量都含有與原來的對象相同的值,除去那些引用其他對象的變量。那些引用其他對象的變量將指向被復制過的新對象,而不再是原有的那些被引用的對象。換言之,除了淺度克隆要克隆的值外,還負責克隆引用類型的數據,基本上就是被克隆實例所有的屬性的數據都會被克隆出來。

淺拷貝實例↓↓↓:

package com.designpattern.Prototype.shallowcopy;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 計划
 * @author Json<<json1990@foxmail.com>>
 */
public class Plan implements Cloneable {
    //計划名稱
    private String name;
    //任務級別
    private int level;
    //開始時間
    private Date startdate;
    //截止時間
    private Date enddate;
    //執行人員
    private List<String> executors = new ArrayList<String>();
    
    @Override
    public Plan clone(){
        try {
            return (Plan) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }

    public String getName() {
        return name;
    }

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

    public Date getStartdate() {
        return startdate;
    }

    public void setStartdate(Date startdate) {
        this.startdate = startdate;
    }

    public Date getEnddate() {
        return enddate;
    }

    public void setEnddate(Date enddate) {
        this.enddate = enddate;
    }

    public List<String> getExecutors() {
        return executors;
    }

    public void setExecutors(List<String> executors) {
        this.executors = executors;
    }

    public int getLevel() {
        return level;
    }
    
    public void setLevel(int level) {
        this.level = level;
    }

    @Override
    public String toString() {
        return "[name=" + name + ", level=" + level + ", startdate=" + startdate + ", enddate=" + enddate
                + ", executors=" + executors + "]";
    }
}

測試:

package com.designpattern.Prototype.shallowcopy;

import java.util.ArrayList;
import java.util.List;

import com.designpattern.utils.DateFormatUtil;

/**
 * 測試
 * @author Json<<json1990@foxmail.com>>
 */
public class Client {
    public static void main(String[] args) {
        List<String> executors = new ArrayList<String>();
        executors.add("張三");
        executors.add("李四");
        
        Plan plan = new Plan();
        plan.setName("重構前端登錄界面");
        plan.setLevel(1);
        plan.setStartdate(DateFormatUtil.stringToDate(DateFormatUtil.YYYYMMDD,"2017-08-07"));
        plan.setEnddate(DateFormatUtil.stringToDate(DateFormatUtil.YYYYMMDD,"2017-08-09"));
        plan.setExecutors(executors);
        
        Plan plan2 = plan.clone();
        plan2.setName("后端接口改造");
        plan2.setLevel(2);
        plan2.setStartdate(DateFormatUtil.stringToDate(DateFormatUtil.YYYYMMDD,"2017-08-10"));
        plan2.setEnddate(DateFormatUtil.stringToDate(DateFormatUtil.YYYYMMDD,"2017-08-12"));
        
        System.out.println("地址是否一樣?"+(plan == plan2));
        System.out.println("plan.getName() == plan2.getName() "+(plan.getName() == plan2.getName()));
        System.out.println("plan.getLevel() == plan2.getLevel() "+(plan.getLevel() == plan2.getLevel()));
        System.out.println("plan.getStartdate() == plan2.getStartdate() "+(plan.getStartdate() == plan2.getStartdate()));
        System.out.println("plan.getEnddate() == plan2.getEnddate() "+(plan.getEnddate() == plan2.getEnddate()));
        System.out.println("plan.getExecutors() == plan2.getExecutors() "+(plan.getExecutors() == plan2.getExecutors()));
        System.out.println("plan:"+plan.toString());
        System.out.println("plan2:"+plan2.toString());
        
        //plan任務比較重,在給plan添加一個人
        executors.add("王五");
        plan.setExecutors(executors);
        
        System.out.println();
        System.out.println("地址是否一樣?"+(plan == plan2));
        System.out.println("plan.getName() == plan2.getName() "+(plan.getName() == plan2.getName()));
        System.out.println("plan.getLevel() == plan2.getLevel() "+(plan.getLevel() == plan2.getLevel()));
        System.out.println("plan.getStartdate() == plan2.getStartdate() "+(plan.getStartdate() == plan2.getStartdate()));
        System.out.println("plan.getEnddate() == plan2.getEnddate() "+(plan.getEnddate() == plan2.getEnddate()));
        System.out.println("plan.getExecutors() == plan2.getExecutors() "+(plan.getExecutors() == plan2.getExecutors()));
        System.out.println("plan:"+plan.toString());
        System.out.println("plan2:"+plan2.toString());
    }
}

結果:

地址是否一樣?false
plan.getName() == plan2.getName() false
plan.getLevel() == plan2.getLevel() false
plan.getStartdate() == plan2.getStartdate() false
plan.getEnddate() == plan2.getEnddate() false
plan.getExecutors() == plan2.getExecutors() true
plan:[name=重構前端登錄界面, level=1, startdate=Mon Aug 07 00:00:00 CST 2017, enddate=Wed Aug 09 00:00:00 CST 2017, executors=[張三, 李四]]
plan2:[name=后端接口改造, level=2, startdate=Thu Aug 10 00:00:00 CST 2017, enddate=Sat Aug 12 00:00:00 CST 2017, executors=[張三, 李四]]

地址是否一樣?false
plan.getName() == plan2.getName() false
plan.getLevel() == plan2.getLevel() false
plan.getStartdate() == plan2.getStartdate() false
plan.getEnddate() == plan2.getEnddate() false
plan.getExecutors() == plan2.getExecutors() true
plan:[name=重構前端登錄界面, level=1, startdate=Mon Aug 07 00:00:00 CST 2017, enddate=Wed Aug 09 00:00:00 CST 2017, executors=[張三, 李四, 王五]]
plan2:[name=后端接口改造, level=2, startdate=Thu Aug 10 00:00:00 CST 2017, enddate=Sat Aug 12 00:00:00 CST 2017, executors=[張三, 李四, 王五]]

通過上面的實例,大家發現沒有,對於引用類型List,修改plan的List,plan2也隨着變化了,這就是淺拷貝帶來的問題;

 

深拷貝實例(修改淺拷貝實例)↓↓↓:

package com.designpattern.Prototype.deepcopy;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 計划
 * @author Json<<json1990@foxmail.com>>
 */
public class Plan implements Cloneable {
    //計划名稱
    private String name;
    //任務級別
    private int level;
    //開始時間
    private Date startdate;
    //截止時間
    private Date enddate;
    //執行人員
    private List<String> executors = new ArrayList<String>();
    
    @Override
    public Plan clone(){
        try {
            Plan plan = (Plan) super.clone();
            //引用類型的屬性,需要處理
            if(this.getExecutors() != null){
                List<String> _executors = new ArrayList<String>();
                for(String s : this.getExecutors()){
                    _executors.add(s);
                }
                plan.setExecutors(_executors);
            }
            
            return plan;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }

    public String getName() {
        return name;
    }

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

    public Date getStartdate() {
        return startdate;
    }

    public void setStartdate(Date startdate) {
        this.startdate = startdate;
    }

    public Date getEnddate() {
        return enddate;
    }

    public void setEnddate(Date enddate) {
        this.enddate = enddate;
    }

    public List<String> getExecutors() {
        return executors;
    }

    public void setExecutors(List<String> executors) {
        this.executors = executors;
    }

    public int getLevel() {
        return level;
    }
    
    public void setLevel(int level) {
        this.level = level;
    }
    
    @Override
    public String toString() {
        return "[name=" + name + ", level=" + level + ", startdate=" + startdate + ", enddate=" + enddate
                + ", executors=" + executors + "]";
    }
    
}

測試(沒改動):

package com.designpattern.Prototype.deepcopy;

import java.util.ArrayList;
import java.util.List;

import com.designpattern.utils.DateFormatUtil;

/**
 * 測試
 * @author Json<<json1990@foxmail.com>>
 */
public class Client {
    public static void main(String[] args) {
        List<String> executors = new ArrayList<String>();
        executors.add("張三");
        executors.add("李四");
        
        Plan plan = new Plan();
        plan.setName("重構前端登錄界面");
        plan.setLevel(1);
        plan.setStartdate(DateFormatUtil.stringToDate(DateFormatUtil.YYYYMMDD,"2017-08-07"));
        plan.setEnddate(DateFormatUtil.stringToDate(DateFormatUtil.YYYYMMDD,"2017-08-09"));
        plan.setExecutors(executors);
        
        Plan plan2 = plan.clone();
        plan2.setName("后端接口改造");
        plan2.setLevel(2);
        plan2.setStartdate(DateFormatUtil.stringToDate(DateFormatUtil.YYYYMMDD,"2017-08-10"));
        plan2.setEnddate(DateFormatUtil.stringToDate(DateFormatUtil.YYYYMMDD,"2017-08-12"));
        
        System.out.println("地址是否一樣?"+(plan == plan2));
        System.out.println("plan.getName() == plan2.getName() "+(plan.getName() == plan2.getName()));
        System.out.println("plan.getLevel() == plan2.getLevel() "+(plan.getLevel() == plan2.getLevel()));
        System.out.println("plan.getStartdate() == plan2.getStartdate() "+(plan.getStartdate() == plan2.getStartdate()));
        System.out.println("plan.getEnddate() == plan2.getEnddate() "+(plan.getEnddate() == plan2.getEnddate()));
        System.out.println("plan.getExecutors() == plan2.getExecutors() "+(plan.getExecutors() == plan2.getExecutors()));
        System.out.println("plan:"+plan.toString());
        System.out.println("plan2:"+plan2.toString());
        
        //plan任務比較重,在給plan添加一個人
        executors.add("王五");
        plan.setExecutors(executors);
        
        System.out.println();
        System.out.println("地址是否一樣?"+(plan == plan2));
        System.out.println("plan.getName() == plan2.getName() "+(plan.getName() == plan2.getName()));
        System.out.println("plan.getLevel() == plan2.getLevel() "+(plan.getLevel() == plan2.getLevel()));
        System.out.println("plan.getStartdate() == plan2.getStartdate() "+(plan.getStartdate() == plan2.getStartdate()));
        System.out.println("plan.getEnddate() == plan2.getEnddate() "+(plan.getEnddate() == plan2.getEnddate()));
        System.out.println("plan.getExecutors() == plan2.getExecutors() "+(plan.getExecutors() == plan2.getExecutors()));
        System.out.println("plan:"+plan.toString());
        System.out.println("plan2:"+plan2.toString());
    }
}

結果:

地址是否一樣?false
plan.getName() == plan2.getName() false
plan.getLevel() == plan2.getLevel() false
plan.getStartdate() == plan2.getStartdate() false
plan.getEnddate() == plan2.getEnddate() false
plan.getExecutors() == plan2.getExecutors() false
plan:[name=重構前端登錄界面, level=1, startdate=Mon Aug 07 00:00:00 CST 2017, enddate=Wed Aug 09 00:00:00 CST 2017, executors=[張三, 李四]]
plan2:[name=后端接口改造, level=2, startdate=Thu Aug 10 00:00:00 CST 2017, enddate=Sat Aug 12 00:00:00 CST 2017, executors=[張三, 李四]]

地址是否一樣?false
plan.getName() == plan2.getName() false
plan.getLevel() == plan2.getLevel() false
plan.getStartdate() == plan2.getStartdate() false
plan.getEnddate() == plan2.getEnddate() false
plan.getExecutors() == plan2.getExecutors() false
plan:[name=重構前端登錄界面, level=1, startdate=Mon Aug 07 00:00:00 CST 2017, enddate=Wed Aug 09 00:00:00 CST 2017, executors=[張三, 李四, 王五]]
plan2:[name=后端接口改造, level=2, startdate=Thu Aug 10 00:00:00 CST 2017, enddate=Sat Aug 12 00:00:00 CST 2017, executors=[張三, 李四]]

到這里,大家對原型模式的淺拷貝、深拷貝有了一定了解了吧!

4、優缺點

優點:

  1、對客戶端隱藏具體的實現類型:原型模式的客戶端,只知道原型接口的類型,並不知道具體的實現類型,從而減少了客戶端對這些具體實現類型的依賴。

  2、在運行時動態改變具體的實現類型:原型模式可以在運行期間,由客戶來注冊符合原型接口的實現類型,也可以動態的改變具體的實現類型,看起來接口沒有任何變化,但其實運行的已經是另外一個類實例了。因為克隆一個原型就類似於實例化一個類。

缺點:

  深度克隆方法實現會比較困難:原型模式最大的缺點就在於每個原型的子類都必須實現clone的操作,尤其在包含引用類型的對象時,clone方法會比較麻煩,必須要能夠遞歸的讓所有的相關對象都要正確的實現克隆。

5、應用場景

  1、一個系統想要獨立於它想要使用的對象時,可以使用原型模式,讓系統只面向接口編程,在系統需要新的對象的時候,可以通過克隆原型來得到;

  2、需要實例化的類是在運行時刻動態指定時,可以使用原型模式,通過克隆原型來得到需要的實例;

  3、創建新對象成本較大(如初始化需要占用較長的時間,占用太多的CPU資源或網絡資源),新的對象可以通過原型模式對已有對象進行復制來獲得,如果是相似對象,則可以對其成員變量稍作修改。

  .......

6、擴展總結

Java中對象的創建:

clone就是復制,在Java中,clone方法被對象調用,所以會復制對象。所謂的復制對象,首先要分配一個和源對象同樣大小的空間,在這個空間中創建一個新的對象。

那么在java語言中,有幾種方式可以創建對象呢?

  1、使用new操作符創建一個對象

  2、使用clone方法復制一個對象

那么這兩種方式有什么相同和不同呢?

  ▶ new操作符的本意是分配內存。程序執行到new操作符時,首先去看new操作符后面的類型,因為知道了類型,才能知道要分配多大的內存空間。分配完內存之后,再調用構造函數,填充對象的各個域,這一步叫做對象的初始化,構造方法返回后,一個對象創建完畢,可以把他的引用(地址)發布到外部,在外部就可以使用這個引用操縱這個對象。

  ▶ clone在第一步是和new相似的,都是分配內存,調用clone方法時,分配的內存和源對象(即調用clone方法的對象)相同,然后再使用原對象中對應的各個域,填充新對象的域,填充完成之后,clone方法返回,一個新的相同的對象被創建,同樣可以把這個新對象的引用發布到外部。


clone 方法需要滿足的幾個原則:

  x.clone() != x,兩個對象不是一個;

  x.clone().getClass() == x.getClass(),兩個對象的class是同一個;

  x.clone().equals(x),兩個應該滿足equals()方法條件;

注意:

  Object類的clone方法只會拷貝java中的8中基本類型以及他們的封裝類型,另外還有String類型。對於數組、容器對象、引用對象等都不會拷貝,這就是淺拷貝。如果要實現深拷貝,必須將原型模式中的數組、容器對象、引用對象等另行拷貝。

 

PS: 要使用clone方法,類的成員變量上不要增加final關鍵字。

PS:深拷貝和淺拷貝建議不要混合使用,特別是是在涉及到類的繼承,父類有多個引用的情況就非常的復雜,建議的方案是深拷貝和淺拷貝分開實現。

PS:java實現真正的深拷貝,可以通過實現Serializable接口用序列化的辦法來實現。

 

PS:源碼地址   https://github.com/JsonShare/DesignPattern/tree/master 

  

PS:原文地址 http://www.cnblogs.com/JsonShare/p/7300124.html

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM