設計模式-原型(prototype)

来源:http://www.cnblogs.com/bateman6988/archive/2017/10/25/7707736.html
-Advertisement-
Play Games

一、概念 用原型實例指定創建對象的種類,並通過拷貝這些原型創建新的對象。 二、模式動機 當已有一個對像,暫且稱之為原型對象,需要一個新的對像,該對像和已有的原型對像具有相同的類型,且裡面的屬性大部分相同,或者只有個別不同時,這時就可以用原型模式,克隆原型對像,產生一個新的對像,並對新的對像屬性進行適 ...


 一、概念

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

二、模式動機

  當已有一個對像,暫且稱之為原型對象,需要一個新的對像,該對像和已有的原型對像具有相同的類型,且裡面的屬性大部分相同,或者只有個別不同時,這時就可以用原型模式,克隆原型對像,產生一個新的對像,並對新的對像屬性進行適當的修改,已適應系統需求。比如新生產的同一批次同一型號的筆記本電腦,如果每個電腦都是一個實例對像,這些電腦配置都是相同的,唯一不同可能是序列號不同,如何實例化所有的電腦,這時就可以用一個原型電腦,去克隆出所有的電腦,只需對克隆出來的新電腦設置正確的序列號就可以了。

三、模式的結構

  

   角色分析:

    1.IPrototype:聲明一個克隆自身的介面

    2.ConcretePrototype(ConcretePrototypeA,ConcretePrototypeA): 實現了IPrototype介面, 這些類真正實現了克隆自身的功能

    3.Client:讓一個原型對象克隆自身產生一個新的對象。

  

樣例代碼:

package prototype;

/**
 * 聲明一個克隆自身的介面,所有實現該介面的類實例,都可以克隆自身產生一個新的對象
* @ClassName: IPrototype 
* @author beteman6988
* @date 2017年10月23日 下午9:47:35 
*
 */
public interface IPrototype {
    
    /**
     * 實現克隆自身的介面方法
    * @Title: clone 
    * @param @return   
    * @return IPrototype    
    * @throws
     */
    public IPrototype clone();

}
package prototype;

/**
 * 實現了克隆介面的具體實現對象
* @ClassName: ConcretePrototype 
* @author beteman6988
* @date 2017年10月23日 下午9:51:13 
*
 */
public class ConcretePrototypeA implements IPrototype {

    /**
     * 實現克隆功能的具本方法
     */
    @Override
    public IPrototype clone() {
        //最簡單的方式就是new 一個新對像,並一一將自已的屬性值複製到新的對像裡面
        IPrototype cloneObj=new ConcretePrototypeA();
        //將本對像的屬性值賦於新的對像
        //如   cloneObj.setProperty(this.propety); 等
        return cloneObj;
    }

}
package prototype;

/**
 * 實現了克隆介面的具體實現對象
* @ClassName: ConcretePrototype 
* @author beteman6988
* @date 2017年10月23日 下午9:51:13 
*
 */
public class ConcretePrototypeB implements IPrototype {
    
    /**
     * 實現克隆功能的具本方法
     */
    @Override
    public IPrototype clone() {
        //最簡單的方式就是new 一個新對像,並一一將自已的屬性值複製到新的對像裡面
        IPrototype cloneObj=new ConcretePrototypeB();
        //將本對像的屬性值賦於新的對像
        //如   cloneObj.setProperty(this.propety); 等
        return cloneObj;
    }

}
package prototype;

/**
 * 客戶端程式,從已有實例克隆出一個新的對象
* @ClassName: Client 
* @author beteman6988
* @date 2017年10月23日 下午10:06:18 
*
 */
public class Client {
    private IPrototype instance=new ConcretePrototypeA();
    
    
    private void operation() {
        //從已有實例 instance 克隆出一個新的對象 cloneObj
        IPrototype  cloneObj=instance.clone();
    }
    
}

  

 關於淺度克隆、深度克隆及java對該原型模式的支持

  1.淺度克隆與深度克隆:在講原型模式是,淺度克隆與深度克隆是逃不開的話題,上面的示例都是淺度克隆,那麼什麼是淺度克隆和深度克隆呢?

    淺度克隆:只負責按值傳遞的數據,如基本數據類型和String  ,如果是引用,則原型對像和克隆出的對像指的是同一個引用地址。

      深度克隆:除了淺度克隆要克隆的值外,引用對像也會被克隆,克隆出的對像和原型對像中的引用是不同的,指上不同的地址空間。

  2. java對淺度克隆的支持:java.lang.Object.clone()方法,該方法為protected native方法,表明繼承於他的類都可以調用該方法,又由於java中的所有類都繼承於java.lang.Object,所以說java中的所有類都可以以super.clone()的方式調用java.lang.Object.clone()方法。當子類通過調用java.lang.Object.clone()方法實現克隆時,子類必須實現Cloneable標識介面,該標識介面的作用就是在運行時通知java虛擬機可以安全的在這個類上使用clone()方法,通過該方法得到一個對像的克隆,如下圖所示:

            

  代碼如下:

  

package prototype.cloneable;

/**
 * 聲明一個克隆自身的介面,所有實現該介面的類實例,都可以克隆自身產生一個新的對象
* @ClassName: IPrototype 
* @author beteman6988
* @date 2017年10月23日 下午9:47:35 
*
 */
public interface IPrototype extends Cloneable {
    
    /**
     * 實現克隆自身的介面方法
    * @Title: clone 
    * @param @return   
    * @return IPrototype    
    * @throws
     */
    public IPrototype clone();

}
package prototype.cloneable;
/**
 * 實現了克隆介面的具體實現對象
* @ClassName: ConcretePrototype 
* @author beteman6988
* @date 2017年10月23日 下午9:51:13 
*
 */
public class ConcretePrototypeA implements IPrototype {

    /**
     * 實現克隆功能的具本方法
     */
    @Override
    public IPrototype clone() {
        
        try {
            return     (IPrototype)super.clone();
            
        } catch ( Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return null;
        }
    }
}
package prototype.cloneable;

/**
 * 客戶端程式  跟據已有實例,克隆出一個新的實例
* @ClassName: Client 
* @author beteman6988
* @date 2017年10月23日 下午11:37:18 
*
 */
public class Client {
    private IPrototype instance=new ConcretePrototypeA();
    public void option() {
        IPrototype p=instance.clone();
    }
    
}

     3.java對深度克隆的支持:在java裡面利用串列化Serilization可以實現深度克隆,他要求原型類及原型類裡面的引用類都需要實現Serializable標識介面,他的實現思想是通過將原型對像寫到流裡面,然後再從流里讀出來重建對像。還是基於上面的例子,如下圖:

      

package prototype;

import java.io.Serializable;

public class A  implements Serializable {
    
}
package prototype;

import java.io.Serializable;

/**
 * 聲明一個克隆自身的介面,所有實現該介面的類實例,都可以克隆自身產生一個新的對象
* @ClassName: IPrototype 
* @author beteman6988
* @date 2017年10月23日 下午9:47:35 
*
 */
public interface IPrototype extends Serializable {
    
    /**
     * 實現克隆自身的介面方法
    * @Title: clone 
    * @param @return   
    * @return IPrototype    
    * @throws
     */
    public IPrototype clone();

}
package prototype;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * 實現了克隆介面的具體實現對象
* @ClassName: ConcretePrototype 
* @author beteman6988
* @date 2017年10月23日 下午9:51:13 
*
 */
public class ConcretePrototypeA implements IPrototype {
    
    private A a =new A();

    /**
     * 實現克隆功能的具本方法
     */
    @Override
    public IPrototype clone() {
        
        try {
            //將對像寫入流中
            ByteArrayOutputStream bo=new ByteArrayOutputStream();
            ObjectOutputStream oo=new ObjectOutputStream(bo);
            oo.writeObject(this);
            
            //將對像從流中讀出
            ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
            ObjectInputStream oi=new ObjectInputStream(bi);
            return (IPrototype)oi.readObject();
            
        } catch ( Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return null;
        }
    }
}
package prototype;

/**
 * 客戶端程式  
* @ClassName: Client 
* @author beteman6988
* @date 2017年10月23日 下午11:37:18 
*
 */
public class Client {
    
    public static void main(String[] args) {
        IPrototype prototype=new ConcretePrototypeA();
        IPrototype copy= prototype.clone();
    }
}

通過調試見下圖,可以看出copy對象及內部的a 引用對象與原型對象是不同的對象,如下圖示:

        

 

    4.java通過逐級淺克隆實現深度克隆:通過java.lang.Object.clone()對java對像及內部的引用逐級克隆也是可以實現的,難點只是無法確定克隆的層次,因為引用裡面還有可能有引用,引用的層次無法確定。 

四、模式樣例

  現實生活中複印機和細胞分裂就是很貼切的例子,當我們將一份文件放到複印機中,複印機可以將原件自身複印(克隆)出另一份文件,兩份文件內容相同,但是是不同的兩個實體。如下圖所示:

               

 

  

package prototype;

/**
 * 具有複印功能的介面
* @ClassName: CopyAble 
* @author beteman6988
* @date 2017年10月23日 下午11:19:45 
*
 */
public interface CopyAble {
    
    public CopyAble copy();

}
/**
 * 可以複製的一張紙
* @ClassName: Paper 
* @author beteman6988
* @date 2017年10月23日 下午11:28:57 
*
 */
public class Paper implements CopyAble {
    
    private String header;  //文件頭
    private String content; //文件內容
    private String footer;  //文件尾

    
    /**
     * 複製出內容一樣的一張紙
     */
    @Override
    public CopyAble copy() {
        Paper anotherPaper=new Paper();
        anotherPaper.setHeader(this.header);
        anotherPaper.setContent(this.content);
        anotherPaper.setFooter(this.footer);
        return anotherPaper;
    }

    public String getHeader() {
        return header;
    }

    public void setHeader(String header) {
        this.header = header;
    }

    public String getContent() {
        return content;
    }

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

    public String getFooter() {
        return footer;
    }

    public void setFooter(String footer) {
        this.footer = footer;
    }
    
    
}
/**
 * 可以複製的一張紙
* @ClassName: Paper 
* @author beteman6988
* @date 2017年10月23日 下午11:28:57 
*
 */
public class Paper implements CopyAble {
    
    private String header;  //文件頭
    private String content; //文件內容
    private String footer;  //文件尾

    
    /**
     * 複製出內容一樣的一張紙
     */
    @Override
    public CopyAble copy() {
        Paper anotherPaper=new Paper();
        anotherPaper.setHeader(this.header);
        anotherPaper.setContent(this.content);
        anotherPaper.setFooter(this.footer);
        return anotherPaper;
    }

    public String getHeader() {
        return header;
    }

    public void setHeader(String header) {
        this.header = header;
    }

    public String getContent() {
        return content;
    }

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

    public String getFooter() {
        return footer;
    }

    public void setFooter(String footer) {
        this.footer = footer;
    }
    
    
}
/**
 * 可複製的一張照片
* @ClassName: Picture 
* @author beteman6988
* @date 2017年10月23日 下午11:30:51 
*
 */
public class Picture implements CopyAble {

    private String data; //照片內容
    
    /**
     * 複製出內容一模一樣的一張照片
     */
    @Override
    public CopyAble copy() {
        // TODO Auto-generated method stub
        return null;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
    
}
package prototype;

/**
 * 複印機類,可以複印一切可以複印的東西(即實現CopyAble的實現類)
* @ClassName: CopyMachine 
* @author beteman6988
* @date 2017年10月23日 下午11:34:35 
*
 */
public class CopyMachine {
    
    /**
     * 對傳入的可複製對像進行複製
    * @Title: runCopy 
    * @param @param source
    * @param @return   
    * @return CopyAble    
    * @throws
     */
    public CopyAble runCopy(CopyAble source) {
        return source.copy();
    }

}
package prototype;

/**
 * 客戶端程式  使用複印機,對現有的可複製資料進行複製
* @ClassName: Client 
* @author beteman6988
* @date 2017年10月23日 下午11:37:18 
*
 */
public class Client {
    
    public static void main(String[] args) {
        
         CopyMachine machine=new CopyMachine();
        
         Paper aPaper=new Paper();
         aPaper.setHeader("文件頭內空");
         aPaper.setContent("文件內容");
         aPaper.setFooter("文件尾內容");
         
         Paper anotherPaper= (Paper) machine.runCopy(aPaper); //複印機複印出另一文件
         System.out.println(anotherPaper.getHeader());
         System.out.println(anotherPaper.getContent());
         System.out.println(anotherPaper.getFooter());
   

      System.out.println(aPaper==anotherPaper);
      System.out.println(aPaper.getClass()==anotherPaper.getClass());


         
    }
    
}

運行結果如下:

文件頭內空
文件內容
文件尾內容
false
true

  

五、模式的約束

  對於第三方客戶提供的實現類,這種類往往無權進行修改,這時如何實現對該類實例的克隆,是相當麻煩,這時可以藉助一些通用性比較好的工具類來完成,如apache 的org.apache.commons.beanutils.BeanUtils.copyProperties(Object dest, Object orig)

 

  對於克隆出的新對象和原對象之間需滿足以下條件約束

  1 對於任何對象  x.clone()!=x  克隆對像和原對象不是同一個對像  ,該條件必須滿足

  2. x.clone().getClass()=x.getClass() ,克隆對像和原對像的類型必須相同

  3.x.clone().equals(x)  , 可選

  

六、模式的變體與擴展

   在實際的開發中,對於需要單獨實現克隆的情況相對較少,經常使用的是一些寫好的通用工具類,如apache 的org.apache.commons.beanutils.BeanUtils.copyProperties(Object dest, Object orig) ,其實現原理是通過java的反射機制來實現的,拿來就可以直接使用。

七、與其它模式的關係

  如抽像工廠模式或工廠方法模式,這些模式關註點是要產生什麼樣的對像,對於如何產生這樣的對像,適當的情況下就可以結合原型模式,如通過已有的對像產生想要的對像。

八、模式優缺點

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

         缺點:在實現深度克隆時比較麻煩,對於對像裡面有引用,引用裡面可能還有引用,每個引用層級的類都必須正確實現clone()方法才能讓所有層級的對像正確的實現克隆。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • eclipse 中main()函數中的String[] args如何使用? 右擊你的項目,選擇run as中選擇 run configuration,選擇arguments總的program arguments,在其中輸入即可。 靜態的主方法怎樣才能調用非static的方法——通過生成對象? 在類中 ...
  • 1
    1 ...
  • CGLib動態代理 使用JDK創建代理有一個限制,即它只能為介面創建代理實例,這一點可以從Proxy的介面方法newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler handler)中看得很清楚:第二個入參in ...
  • 聲明:本篇文章僅僅以游戲《絕地求生》作為一個參考話題來介紹面向對象編程,只是作為學術引用,其製作的非常簡易的程式也不會作為商業用途,與藍洞公司無關。 《絕地求生》最近很火,籠絡了全球各地一大批玩家玩這個游戲,實話說,這游戲真不錯,有了這個游戲後,當你去網吧時看到的屏幕終於不再清一色的《英雄聯盟》界面 ...
  • JTable更新內容的方法 DefaultTableModel dtm=new DefaultTableModel(data,head);//定義表格模型 jt.setModel(dtm);或jt=new JTable(dtm);//根據表格模型創建表格 DefaultTableModel dtm2 ...
  • 1051 最大子矩陣和 基準時間限制:2 秒 空間限制:131072 KB 分值: 40 難度:4級演算法題 收藏 關註 1051 最大子矩陣和 基準時間限制:2 秒 空間限制:131072 KB 分值: 40 難度:4級演算法題 1051 最大子矩陣和 基準時間限制:2 秒 空間限制:131072 K ...
  • 微信公眾平臺介面調試工具 : https://mp.weixin.qq.com/debug/ 下麵模擬手機微信 向公眾號發信息 測試結果: ...
  • 1.反射機制的理解 在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法 對於任意一個對象,都能夠調用它的任意一個方法和屬性 類中有什麼信息,利用反射機制就能可以獲得什麼信息,不過前提是得知道類的名字 2.反射機制的作用 在運行時判斷任意一個對象所屬的類; 在運行時獲取類的實例對象; 在運 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...